mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
75 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38c264e38d | ||
|
|
8da6b882fc | ||
|
|
6e03c2ebfc | ||
|
|
a5cb5ed896 | ||
|
|
32bef7b099 | ||
|
|
af0359132e | ||
|
|
85990879de | ||
|
|
9436a4c3a5 | ||
|
|
efc786e318 | ||
|
|
ed299b35e9 | ||
|
|
0defe9401f | ||
|
|
b66678f028 | ||
|
|
b0cfd4292f | ||
|
|
8e390685cd | ||
|
|
9bcacdca4a | ||
|
|
c1969402f1 | ||
|
|
50bf22a4c7 | ||
|
|
cf93044d3f | ||
|
|
33404add38 | ||
|
|
49ba74c38f | ||
|
|
3ed5db9819 | ||
|
|
4535204d1e | ||
|
|
18c656a555 | ||
|
|
42c28b5b95 | ||
|
|
9fc47fdf05 | ||
|
|
9ef34599ce | ||
|
|
bd3e5c6917 | ||
|
|
7e9bed1e53 | ||
|
|
ff2215929c | ||
|
|
36d53e9788 | ||
|
|
6b9db7d40d | ||
|
|
30f805f5a9 | ||
|
|
64448ef218 | ||
|
|
6e312e0420 | ||
|
|
1f7b86d34f | ||
|
|
ae00fd5782 | ||
|
|
62f6f02467 | ||
|
|
55ee89314f | ||
|
|
b2f985aa03 | ||
|
|
b3a34c0138 | ||
|
|
1f06b09d38 | ||
|
|
758914a80a | ||
|
|
f2566abdcd | ||
|
|
cb6d479350 | ||
|
|
846930a4f9 | ||
|
|
e788d89b18 | ||
|
|
07f68cfe2f | ||
|
|
1c6ef51925 | ||
|
|
9995bc4d9f | ||
|
|
accb931831 | ||
|
|
42ac83c814 | ||
|
|
9ee9063c4d | ||
|
|
8c38b361ea | ||
|
|
cb919b2de5 | ||
|
|
679ea7c58f | ||
|
|
21ebbd25f8 | ||
|
|
3c232ac5b6 | ||
|
|
d36d2408d7 | ||
|
|
9fd59f850c | ||
|
|
4cc7d1e74d | ||
|
|
66f4353c3e | ||
|
|
0acc83b066 | ||
|
|
943d7ca6b9 | ||
|
|
65d1c7376b | ||
|
|
871a624313 | ||
|
|
8cf515120f | ||
|
|
30c5db92e6 | ||
|
|
19ebc40bdb | ||
|
|
4db2e6baf9 | ||
|
|
a9c1369cbf | ||
|
|
dd478d7cd4 | ||
|
|
53e7cc7f72 | ||
|
|
e5d3a0a931 | ||
|
|
23e6b12326 | ||
|
|
5a54955941 |
12
CHANGELOG
12
CHANGELOG
@@ -1,3 +1,15 @@
|
||||
KeepassDX (2.5.0.0beta7)
|
||||
* Rebuild Notifications
|
||||
* Change links to https
|
||||
* Add extended Ascii (ñæËÌÂÝÜ...)
|
||||
* Upgrade custom visibility font
|
||||
* Best fingerprint error management
|
||||
* Add setting to prevent the password copy
|
||||
* Fix bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta6)
|
||||
* Fix crash
|
||||
|
||||
KeepassDX (2.5.0.0beta5)
|
||||
* Autofill (Android O)
|
||||
* Deletion for group
|
||||
|
||||
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode = 5
|
||||
versionName = "2.5.0.0beta5"
|
||||
versionCode = 7
|
||||
versionName = "2.5.0.0beta7"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.keepassdroid.tests"
|
||||
|
||||
@@ -42,7 +42,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testName() {
|
||||
assertTrue("Name was " + mPE.title, mPE.title.equals("Amazon"));
|
||||
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
|
||||
}
|
||||
|
||||
public void testPassword() throws UnsupportedEncodingException {
|
||||
@@ -54,7 +54,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
|
||||
|
||||
public void testCreation() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(mPE.tCreation.getJDate());
|
||||
cal.setTime(mPE.getCreationTime().getDate());
|
||||
|
||||
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
|
||||
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
|
||||
|
||||
@@ -19,10 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.tests;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.keepassdroid.database.AutoType;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
import com.keepassdroid.database.PwIconCustom;
|
||||
@@ -30,29 +27,33 @@ import com.keepassdroid.database.PwIconStandard;
|
||||
import com.keepassdroid.database.security.ProtectedBinary;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PwEntryTestV4 extends TestCase {
|
||||
public void testAssign() {
|
||||
PwEntryV4 entry = new PwEntryV4();
|
||||
|
||||
entry.additional = "test223";
|
||||
entry.setAdditional("test223");
|
||||
|
||||
entry.autoType = entry.new AutoType();
|
||||
entry.autoType.defaultSequence = "1324";
|
||||
entry.autoType.enabled = true;
|
||||
entry.autoType.obfuscationOptions = 123412432109L;
|
||||
entry.autoType.put("key", "value");
|
||||
entry.setAutoType(new AutoType());
|
||||
entry.getAutoType().defaultSequence = "1324";
|
||||
entry.getAutoType().enabled = true;
|
||||
entry.getAutoType().obfuscationOptions = 123412432109L;
|
||||
entry.getAutoType().put("key", "value");
|
||||
|
||||
entry.backgroupColor = "blue";
|
||||
entry.binaries.put("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
||||
entry.customIcon = new PwIconCustom(UUID.randomUUID(), new byte[0]);
|
||||
entry.foregroundColor = "red";
|
||||
entry.history.add(new PwEntryV4());
|
||||
entry.icon = new PwIconStandard(5);
|
||||
entry.overrideURL = "override";
|
||||
entry.parent = new PwGroupV4();
|
||||
entry.setBackgroupColor("blue");
|
||||
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
||||
entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0]));
|
||||
entry.setForegroundColor("red");
|
||||
entry.addToHistory(new PwEntryV4());
|
||||
entry.setIcon(new PwIconStandard(5));
|
||||
entry.setOverrideURL("override");
|
||||
entry.setParent(new PwGroupV4());
|
||||
entry.addField("key2", new ProtectedString(false, "value2"));
|
||||
entry.url = "http://localhost";
|
||||
entry.uuid = UUID.randomUUID();
|
||||
entry.setUrl("http://localhost");
|
||||
entry.setUUID(UUID.randomUUID());
|
||||
|
||||
PwEntryV4 target = new PwEntryV4();
|
||||
target.assign(entry);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PwGroupTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testGroupName() {
|
||||
assertTrue("Name was " + mPG.name, mPG.name.equals("Internet"));
|
||||
assertTrue("Name was " + mPG.getName(), mPG.getName().equals("Internet"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,19 +32,21 @@ public class EntryV4 extends TestCase {
|
||||
db.historyMaxItems = 2;
|
||||
|
||||
PwEntryV4 entry = new PwEntryV4();
|
||||
entry.setTitle("Title1", db);
|
||||
entry.setUsername("User1", db);
|
||||
entry.startToDecodeReference(db);
|
||||
entry.setTitle("Title1");
|
||||
entry.setUsername("User1");
|
||||
entry.createBackup(db);
|
||||
|
||||
entry.setTitle("Title2", db);
|
||||
entry.setUsername("User2", db);
|
||||
entry.setTitle("Title2");
|
||||
entry.setUsername("User2");
|
||||
entry.createBackup(db);
|
||||
|
||||
entry.setTitle("Title3", db);
|
||||
entry.setUsername("User3", db);
|
||||
entry.setTitle("Title3");
|
||||
entry.setUsername("User3");
|
||||
entry.createBackup(db);
|
||||
|
||||
PwEntryV4 backup = entry.history.get(0);
|
||||
PwEntryV4 backup = entry.getHistory().get(0);
|
||||
entry.endToDecodeReference(db);
|
||||
assertEquals("Title2", backup.getTitle());
|
||||
assertEquals("User2", backup.getUsername());
|
||||
}
|
||||
|
||||
@@ -27,15 +27,16 @@ import android.content.res.AssetManager;
|
||||
import android.test.AndroidTestCase;
|
||||
import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.load.ImporterV4;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
import com.keepassdroid.utils.SprEngineV4;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
public class SprEngineTest extends AndroidTestCase {
|
||||
private PwDatabaseV4 db;
|
||||
private SprEngine spr;
|
||||
private SprEngineV4 spr;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -51,7 +52,7 @@ public class SprEngineTest extends AndroidTestCase {
|
||||
|
||||
is.close();
|
||||
|
||||
spr = SprEngine.getInstance(db);
|
||||
spr = new SprEngineV4();
|
||||
}
|
||||
|
||||
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
|
||||
@@ -69,7 +70,7 @@ public class SprEngineTest extends AndroidTestCase {
|
||||
|
||||
private UUID decodeUUID(String encoded) {
|
||||
if (encoded == null || encoded.length() == 0 ) {
|
||||
return PwDatabaseV4.UUID_ZERO;
|
||||
return PwDatabase.UUID_ZERO;
|
||||
}
|
||||
|
||||
byte[] buf = Base64Coder.decode(encoded);
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.tests.database.TestData;
|
||||
@@ -66,7 +65,7 @@ public class SearchTest extends AndroidTestCase {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
editor.putBoolean(ctx.getString(R.string.settings_omitbackup_key), setting);
|
||||
editor.putBoolean("settings_omitbackup_key", setting);
|
||||
editor.commit();
|
||||
|
||||
}
|
||||
|
||||
@@ -133,8 +133,13 @@
|
||||
<activity android:name="com.keepassdroid.settings.SettingsActivity" />
|
||||
<activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity"
|
||||
android:configChanges="orientation|keyboardHidden" />
|
||||
<activity android:name="com.keepassdroid.settings.SettingsAutofillActivity" />
|
||||
|
||||
<service android:name="com.keepassdroid.services.TimeoutService" />
|
||||
<service android:name="com.keepassdroid.timeout.TimeoutService" />
|
||||
<service
|
||||
android:name="com.keepassdroid.notifications.NotificationCopyingService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="com.keepassdroid.autofill.KeeAutofillService"
|
||||
android:label="@string/autofill_service_name"
|
||||
|
||||
@@ -21,26 +21,12 @@
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -50,15 +36,13 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ActivityCompat;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.exception.SamsungClipboardException;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
import com.keepassdroid.notifications.NotificationCopyingService;
|
||||
import com.keepassdroid.notifications.NotificationField;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.tasks.UIToastTask;
|
||||
import com.keepassdroid.timeout.ClipboardHelper;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.Types;
|
||||
@@ -66,35 +50,35 @@ import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.EntryContentsView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable;
|
||||
|
||||
public class EntryActivity extends LockingHideActivity {
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
private final static String TAG = EntryActivity.class.getName();
|
||||
|
||||
public static final int NOTIFY_USERNAME = 1;
|
||||
public static final int NOTIFY_PASSWORD = 2;
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
|
||||
private ImageView titleIconView;
|
||||
private TextView titleView;
|
||||
private EntryContentsView entryContentsView;
|
||||
|
||||
protected PwEntry mEntry;
|
||||
private Timer mTimer = new Timer();
|
||||
private boolean mShowPassword;
|
||||
private NotificationManager mNM;
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
protected boolean readOnly = false;
|
||||
|
||||
private ClipboardHelper clipboardHelper;
|
||||
private boolean firstLaunchOfActivity;
|
||||
|
||||
public static void launch(Activity act, PwEntry pw) {
|
||||
Intent intent = new Intent(act, EntryActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||
Intent intent = new Intent(act, EntryActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +87,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
|
||||
setContentView(R.layout.entry_view);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
|
||||
@@ -131,112 +115,95 @@ public class EntryActivity extends LockingHideActivity {
|
||||
}
|
||||
|
||||
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
invalidateOptionsMenu();
|
||||
|
||||
// Update last access time.
|
||||
mEntry.touch(false, false);
|
||||
|
||||
// Get views
|
||||
titleIconView = (ImageView) findViewById(R.id.entry_icon);
|
||||
titleView = (TextView) findViewById(R.id.entry_title);
|
||||
entryContentsView = (EntryContentsView) findViewById(R.id.entry_contents);
|
||||
titleIconView = findViewById(R.id.entry_icon);
|
||||
titleView = findViewById(R.id.entry_title);
|
||||
entryContentsView = findViewById(R.id.entry_contents);
|
||||
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
|
||||
|
||||
fillData();
|
||||
|
||||
// Setup Edit Buttons
|
||||
View edit = findViewById(R.id.entry_edit);
|
||||
edit.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(EntryActivity.this, mEntry);
|
||||
}
|
||||
|
||||
});
|
||||
edit.setOnClickListener(v -> EntryEditActivity.Launch(EntryActivity.this, mEntry));
|
||||
if (readOnly) {
|
||||
edit.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// If notifications enabled in settings
|
||||
if (isClipboardNotificationsEnable(getApplicationContext())) {
|
||||
// Notification Manager
|
||||
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
// Init the clipboard helper
|
||||
clipboardHelper = new ClipboardHelper(this);
|
||||
firstLaunchOfActivity = true;
|
||||
}
|
||||
|
||||
if (mEntry.getPassword().length() > 0) {
|
||||
// only show notification if password is available
|
||||
Notification password = getNotification(Intents.COPY_PASSWORD, R.string.copy_password);
|
||||
mNM.notify(NOTIFY_PASSWORD, password);
|
||||
}
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mEntry.getUsername().length() > 0) {
|
||||
// only show notification if username is available
|
||||
Notification username = getNotification(Intents.COPY_USERNAME, R.string.copy_username);
|
||||
mNM.notify(NOTIFY_USERNAME, username);
|
||||
// Fill data in resume to update from EntryEditActivity
|
||||
fillData();
|
||||
invalidateOptionsMenu();
|
||||
|
||||
// TODO Start decode
|
||||
|
||||
// If notifications enabled in settings
|
||||
// Don't if application timeout
|
||||
if (firstLaunchOfActivity && !App.isShutdown() && isClipboardNotificationsEnable(getApplicationContext())) {
|
||||
if (mEntry.getUsername().length() > 0
|
||||
|| (mEntry.getPassword().length() > 0 && PreferencesUtil.allowCopyPassword(this))
|
||||
|| mEntry.containsExtraFields()) {
|
||||
// username already copied, waiting for user's action before copy password.
|
||||
Intent intent = new Intent(this, NotificationCopyingService.class);
|
||||
intent.setAction(NotificationCopyingService.ACTION_NEW_NOTIFICATION);
|
||||
if (mEntry.getTitle() != null)
|
||||
intent.putExtra(NotificationCopyingService.EXTRA_ENTRY_TITLE, mEntry.getTitle());
|
||||
// Construct notification fields
|
||||
ArrayList<NotificationField> notificationFields = new ArrayList<>();
|
||||
// Add username if exists to notifications
|
||||
if (mEntry.getUsername().length() > 0)
|
||||
notificationFields.add(
|
||||
new NotificationField(
|
||||
NotificationField.NotificationFieldId.USERNAME,
|
||||
mEntry.getUsername(),
|
||||
getResources()));
|
||||
// Add password to notifications
|
||||
if (PreferencesUtil.allowCopyPassword(this)) {
|
||||
if (mEntry.getPassword().length() > 0)
|
||||
notificationFields.add(
|
||||
new NotificationField(
|
||||
NotificationField.NotificationFieldId.PASSWORD,
|
||||
mEntry.getPassword(),
|
||||
getResources()));
|
||||
}
|
||||
// Add extra fields
|
||||
if (mEntry.allowExtraFields()) {
|
||||
try {
|
||||
int anonymousFieldNumber = 0;
|
||||
for (Map.Entry<String, String> entry : mEntry.getExtraFields().entrySet()) {
|
||||
notificationFields.add(
|
||||
new NotificationField(
|
||||
NotificationField.NotificationFieldId.getAnonymousFieldId()[anonymousFieldNumber],
|
||||
entry.getValue(),
|
||||
entry.getKey(),
|
||||
getResources()));
|
||||
anonymousFieldNumber++;
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
Log.w(TAG, "Only " + NotificationField.NotificationFieldId.getAnonymousFieldId().length +
|
||||
" anonymous notifications are available");
|
||||
}
|
||||
}
|
||||
// Add notifications
|
||||
intent.putParcelableArrayListExtra(NotificationCopyingService.EXTRA_FIELDS, notificationFields);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
// TODO end decode
|
||||
}
|
||||
|
||||
mIntentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if ( action != null) {
|
||||
if (action.equals(Intents.COPY_USERNAME)) {
|
||||
String username = mEntry.getUsername();
|
||||
if (username.length() > 0) {
|
||||
timeoutCopyToClipboard(username);
|
||||
}
|
||||
} else if (action.equals(Intents.COPY_PASSWORD)) {
|
||||
String password = mEntry.getPassword();
|
||||
if (password.length() > 0) {
|
||||
timeoutCopyToClipboard(password);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intents.COPY_USERNAME);
|
||||
filter.addAction(Intents.COPY_PASSWORD);
|
||||
registerReceiver(mIntentReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
// These members might never get initialized if the app timed out
|
||||
if ( mIntentReceiver != null ) {
|
||||
unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
if ( mNM != null ) {
|
||||
try {
|
||||
mNM.cancelAll();
|
||||
} catch (SecurityException e) {
|
||||
// Some android devices give a SecurityException when trying to cancel notifications without the WAKE_LOCK permission,
|
||||
// we'll ignore these.
|
||||
}
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private Notification getNotification(String intentText, int descResId) {
|
||||
|
||||
String desc = getString(descResId);
|
||||
|
||||
Intent intent = new Intent(intentText);
|
||||
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
// no longer supported for api level >22
|
||||
// notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending);
|
||||
// so instead using compat builder and create new notification
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
Notification notify = builder.setContentIntent(pending).setContentText(desc).setContentTitle(getString(R.string.app_name))
|
||||
.setSmallIcon(R.drawable.notify).setTicker(desc).setWhen(System.currentTimeMillis()).build();
|
||||
|
||||
return notify;
|
||||
}
|
||||
firstLaunchOfActivity = false;
|
||||
}
|
||||
|
||||
private void populateTitle(Drawable drawIcon, String text) {
|
||||
titleIconView.setImageDrawable(drawIcon);
|
||||
@@ -247,59 +214,55 @@ public class EntryActivity extends LockingHideActivity {
|
||||
Database db = App.getDB();
|
||||
PwDatabase pm = db.pm;
|
||||
|
||||
mEntry.startToDecodeReference(pm);
|
||||
|
||||
// Assign title
|
||||
populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()),
|
||||
mEntry.getTitle(true, pm));
|
||||
mEntry.getTitle());
|
||||
|
||||
// Assign basic fields
|
||||
entryContentsView.assignUserName(mEntry.getUsername(true, pm));
|
||||
entryContentsView.assignUserNameCopyListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm),
|
||||
getString(R.string.copy_field, getString(R.string.entry_user_name)));
|
||||
}
|
||||
});
|
||||
entryContentsView.assignUserName(mEntry.getUsername());
|
||||
entryContentsView.assignUserNameCopyListener(view ->
|
||||
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
|
||||
getString(R.string.copy_field, getString(R.string.entry_user_name)))
|
||||
);
|
||||
|
||||
entryContentsView.assignPassword(mEntry.getPassword(true, pm));
|
||||
entryContentsView.assignPasswordCopyListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm),
|
||||
getString(R.string.copy_field, getString(R.string.entry_password)));
|
||||
}
|
||||
});
|
||||
entryContentsView.assignPassword(mEntry.getPassword());
|
||||
if (PreferencesUtil.allowCopyPassword(this)) {
|
||||
entryContentsView.assignPasswordCopyListener(view ->
|
||||
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
|
||||
getString(R.string.copy_field, getString(R.string.entry_password)))
|
||||
);
|
||||
}
|
||||
|
||||
entryContentsView.assignURL(mEntry.getUrl(true, pm));
|
||||
entryContentsView.assignURL(mEntry.getUrl());
|
||||
|
||||
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
|
||||
entryContentsView.assignComment(mEntry.getNotes(true, pm));
|
||||
entryContentsView.assignComment(mEntry.getNotes());
|
||||
|
||||
// Assign custom fields
|
||||
if (mEntry.allowExtraFields()) {
|
||||
entryContentsView.clearExtraFields();
|
||||
for (Map.Entry<String, String> field : mEntry.getExtraFields(pm).entrySet()) {
|
||||
for (Map.Entry<String, String> field : mEntry.getExtraFields().entrySet()) {
|
||||
final String label = field.getKey();
|
||||
final String value = field.getValue();
|
||||
entryContentsView.addExtraField(label, value, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(value, getString(R.string.copy_field, label));
|
||||
}
|
||||
});
|
||||
entryContentsView.addExtraField(label, value, view ->
|
||||
clipboardHelper.timeoutCopyToClipboard(value, getString(R.string.copy_field, label)));
|
||||
}
|
||||
}
|
||||
|
||||
// Assign dates
|
||||
entryContentsView.assignCreationDate(mEntry.getCreationTime());
|
||||
entryContentsView.assignModificationDate(mEntry.getLastModificationTime());
|
||||
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime());
|
||||
Date expires = mEntry.getExpiryTime();
|
||||
entryContentsView.assignCreationDate(mEntry.getCreationTime().getDate());
|
||||
entryContentsView.assignModificationDate(mEntry.getLastModificationTime().getDate());
|
||||
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime().getDate());
|
||||
Date expires = mEntry.getExpiryTime().getDate();
|
||||
if ( mEntry.expires() ) {
|
||||
entryContentsView.assignExpiresDate(expires);
|
||||
} else {
|
||||
entryContentsView.assignExpiresDate(getString(R.string.never));
|
||||
}
|
||||
|
||||
mEntry.endToDecodeReference(pm);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -332,26 +295,28 @@ public class EntryActivity extends LockingHideActivity {
|
||||
inflater.inflate(R.menu.database_lock, menu);
|
||||
|
||||
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
|
||||
if (!entryContentsView.isPasswordPresent()) {
|
||||
togglePassword.setVisible(false);
|
||||
} else {
|
||||
changeShowPasswordIcon(togglePassword);
|
||||
if (entryContentsView != null && togglePassword != null) {
|
||||
if (!entryContentsView.isPasswordPresent()) {
|
||||
togglePassword.setVisible(false);
|
||||
} else {
|
||||
changeShowPasswordIcon(togglePassword);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
|
||||
|
||||
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
|
||||
// so mEntry may not be set
|
||||
if (mEntry == null) {
|
||||
gotoUrl.setVisible(false);
|
||||
}
|
||||
else {
|
||||
String url = mEntry.getUrl();
|
||||
if (EmptyUtils.isNullOrEmpty(url)) {
|
||||
// disable button if url is not available
|
||||
gotoUrl.setVisible(false);
|
||||
}
|
||||
}
|
||||
if (gotoUrl != null) {
|
||||
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
|
||||
// so mEntry may not be set
|
||||
if (mEntry == null) {
|
||||
gotoUrl.setVisible(false);
|
||||
} else {
|
||||
String url = mEntry.getUrl();
|
||||
if (EmptyUtils.isNullOrEmpty(url)) {
|
||||
// disable button if url is not available
|
||||
gotoUrl.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -385,9 +350,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
App.setShutdown();
|
||||
setResult(PasswordActivity.RESULT_EXIT_LOCK);
|
||||
finish();
|
||||
lockAndExit();
|
||||
return true;
|
||||
|
||||
case android.R.id.home :
|
||||
@@ -397,30 +360,6 @@ public class EntryActivity extends LockingHideActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void timeoutCopyToClipboard(String text) {
|
||||
timeoutCopyToClipboard(text, "");
|
||||
}
|
||||
|
||||
private void timeoutCopyToClipboard(String text, String toastString) {
|
||||
if (!toastString.isEmpty())
|
||||
Toast.makeText(EntryActivity.this, toastString, Toast.LENGTH_LONG).show();
|
||||
|
||||
try {
|
||||
Util.copyToClipboard(this, text);
|
||||
} catch (SamsungClipboardException e) {
|
||||
showSamsungDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String sClipClear = prefs.getString(getString(R.string.clipboard_timeout_key), getString(R.string.clipboard_timeout_default));
|
||||
|
||||
long clipClearTime = Long.parseLong(sClipClear);
|
||||
|
||||
if ( clipClearTime > 0 ) {
|
||||
mTimer.schedule(new ClearClipboardTask(this, text), clipClearTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
@@ -433,55 +372,4 @@ public class EntryActivity extends LockingHideActivity {
|
||||
*/
|
||||
super.finish();
|
||||
}
|
||||
|
||||
// Setup to allow the toast to happen in the foreground
|
||||
final Handler uiThreadCallback = new Handler();
|
||||
|
||||
// Task which clears the clipboard, and sends a toast to the foreground.
|
||||
private class ClearClipboardTask extends TimerTask {
|
||||
|
||||
private final String mClearText;
|
||||
private final Context mCtx;
|
||||
|
||||
ClearClipboardTask(Context ctx, String clearText) {
|
||||
mClearText = clearText;
|
||||
mCtx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String currentClip = Util.getClipboard(mCtx);
|
||||
|
||||
if ( currentClip.equals(mClearText) ) {
|
||||
try {
|
||||
Util.copyToClipboard(mCtx, "");
|
||||
uiThreadCallback.post(new UIToastTask(mCtx, R.string.ClearClipboard));
|
||||
} catch (SamsungClipboardException e) {
|
||||
uiThreadCallback.post(new UIToastTask(mCtx, R.string.clipboard_error_clear));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showSamsungDialog() {
|
||||
String text = getString(R.string.clipboard_error).concat(System.getProperty("line.separator")).concat(getString(R.string.clipboard_error_url));
|
||||
SpannableString s = new SpannableString(text);
|
||||
TextView tv = new TextView(this);
|
||||
tv.setText(s);
|
||||
tv.setAutoLinkMask(RESULT_OK);
|
||||
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
Linkify.addLinks(s, Linkify.WEB_URLS);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.clipboard_error_title)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setView(tv)
|
||||
.show();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,20 +28,18 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwDate;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwIconStandard;
|
||||
@@ -50,8 +48,8 @@ import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.database.edit.RunnableOnFinish;
|
||||
import com.keepassdroid.database.edit.UpdateEntry;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.fragments.GeneratePasswordDialogFragment;
|
||||
import com.keepassdroid.fragments.IconPickerDialogFragment;
|
||||
import com.keepassdroid.dialogs.GeneratePasswordDialogFragment;
|
||||
import com.keepassdroid.dialogs.IconPickerDialogFragment;
|
||||
import com.keepassdroid.icons.Icons;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
@@ -61,9 +59,6 @@ import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.EntryEditNewField;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -88,12 +83,12 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
|
||||
// Views
|
||||
private ScrollView scrollView;
|
||||
private TextView entryTitleView;
|
||||
private TextView entryUserNameView;
|
||||
private TextView entryUrlView;
|
||||
private TextView entryPasswordView;
|
||||
private TextView entryConfirmationPasswordView;
|
||||
private TextView entryCommentView;
|
||||
private EditText entryTitleView;
|
||||
private EditText entryUserNameView;
|
||||
private EditText entryUrlView;
|
||||
private EditText entryPasswordView;
|
||||
private EditText entryConfirmationPasswordView;
|
||||
private EditText entryCommentView;
|
||||
private ViewGroup entryExtraFieldsContainer;
|
||||
|
||||
/**
|
||||
@@ -102,9 +97,11 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
* @param pw Entry to update
|
||||
*/
|
||||
public static void Launch(Activity act, PwEntry pw) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,9 +110,11 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
* @param pwGroup Group who will contains new entry
|
||||
*/
|
||||
public static void Launch(Activity act, PwGroup pwGroup) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_PARENT, pwGroup.getId());
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_PARENT, pwGroup.getId());
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,23 +122,23 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.entry_edit);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
scrollView = (ScrollView) findViewById(R.id.entry_scroll);
|
||||
scrollView = findViewById(R.id.entry_scroll);
|
||||
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
|
||||
entryTitleView = (TextView) findViewById(R.id.entry_title);
|
||||
entryUserNameView = (TextView) findViewById(R.id.entry_user_name);
|
||||
entryUrlView = (TextView) findViewById(R.id.entry_url);
|
||||
entryPasswordView = (TextView) findViewById(R.id.entry_password);
|
||||
entryConfirmationPasswordView = (TextView) findViewById(R.id.entry_confpassword);
|
||||
entryCommentView = (TextView) findViewById(R.id.entry_comment);
|
||||
entryExtraFieldsContainer = (ViewGroup) findViewById(R.id.advanced_container);
|
||||
entryTitleView = findViewById(R.id.entry_title);
|
||||
entryUserNameView = findViewById(R.id.entry_user_name);
|
||||
entryUrlView = findViewById(R.id.entry_url);
|
||||
entryPasswordView = findViewById(R.id.entry_password);
|
||||
entryConfirmationPasswordView = findViewById(R.id.entry_confpassword);
|
||||
entryCommentView = findViewById(R.id.entry_comment);
|
||||
entryExtraFieldsContainer = findViewById(R.id.advanced_container);
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
Database db = App.getDB();
|
||||
@@ -165,67 +164,47 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
View iconButton = findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
IconPickerDialogFragment.launch(EntryEditActivity.this);
|
||||
}
|
||||
});
|
||||
iconButton.setOnClickListener(v ->
|
||||
IconPickerDialogFragment.launch(EntryEditActivity.this));
|
||||
|
||||
// Generate password button
|
||||
View generatePassword = findViewById(R.id.generate_button);
|
||||
generatePassword.setOnClickListener(new OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
|
||||
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
}
|
||||
});
|
||||
generatePassword.setOnClickListener(v -> {
|
||||
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
|
||||
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
});
|
||||
|
||||
// Save button
|
||||
View save = findViewById(R.id.entry_save);
|
||||
save.setOnClickListener(new View.OnClickListener() {
|
||||
save.setOnClickListener(v -> {
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
mCallbackNewEntry = populateNewEntry();
|
||||
|
||||
public void onClick(View v) {
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
mCallbackNewEntry = populateNewEntry();
|
||||
|
||||
OnFinish onFinish = new AfterSave();
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
RunnableOnFinish task;
|
||||
if ( mIsNew ) {
|
||||
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
});
|
||||
OnFinish onFinish = new AfterSave();
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
RunnableOnFinish task;
|
||||
if ( mIsNew ) {
|
||||
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
});
|
||||
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
View add = findViewById(R.id.add_new_field);
|
||||
add.setVisibility(View.VISIBLE);
|
||||
add.setOnClickListener(new View.OnClickListener() {
|
||||
add.setOnClickListener(v -> {
|
||||
EntryEditNewField ees = new EntryEditNewField(EntryEditActivity.this);
|
||||
ees.setData("", new ProtectedString(false, ""));
|
||||
entryExtraFieldsContainer.addView(ees);
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
EntryEditNewField ees = new EntryEditNewField(EntryEditActivity.this);
|
||||
ees.setData("", new ProtectedString(false, ""));
|
||||
entryExtraFieldsContainer.addView(ees);
|
||||
|
||||
// Scroll bottom
|
||||
scrollView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Scroll bottom
|
||||
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -262,21 +241,18 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
protected PwEntry populateNewEntry() {
|
||||
if (mEntry instanceof PwEntryV4) {
|
||||
// TODO backup
|
||||
PwEntryV4 newEntry = (PwEntryV4) mEntry.clone(true);
|
||||
newEntry.history = (ArrayList<PwEntryV4>) newEntry.history.clone();
|
||||
newEntry.createBackup((PwDatabaseV4) App.getDB().pm);
|
||||
}
|
||||
|
||||
PwEntry newEntry = mEntry.clone(true);
|
||||
|
||||
Date now = Calendar.getInstance().getTime();
|
||||
newEntry.setLastAccessTime(now);
|
||||
newEntry.setLastModificationTime(now);
|
||||
|
||||
PwDatabase db = App.getDB().pm;
|
||||
newEntry.setTitle(entryTitleView.getText().toString(), db);
|
||||
|
||||
PwEntry newEntry = mEntry.clone();
|
||||
|
||||
newEntry.startToDecodeReference(db);
|
||||
|
||||
newEntry.createBackup(db);
|
||||
|
||||
newEntry.setLastAccessTime(new PwDate());
|
||||
newEntry.setLastModificationTime(new PwDate());
|
||||
|
||||
newEntry.setTitle(entryTitleView.getText().toString());
|
||||
if(mSelectedIconID != -1)
|
||||
// or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID));
|
||||
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
|
||||
@@ -286,13 +262,13 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
else {
|
||||
// Keep previous icon, if no new one was selected
|
||||
newEntry.setIcon(mEntry.icon);
|
||||
newEntry.setIcon(mEntry.getIconStandard());
|
||||
}
|
||||
}
|
||||
newEntry.setUrl(entryUrlView.getText().toString(), db);
|
||||
newEntry.setUsername(entryUserNameView.getText().toString(), db);
|
||||
newEntry.setNotes(entryCommentView.getText().toString(), db);
|
||||
newEntry.setPassword(entryPasswordView.getText().toString(), db);
|
||||
newEntry.setUrl(entryUrlView.getText().toString());
|
||||
newEntry.setUsername(entryUserNameView.getText().toString());
|
||||
newEntry.setNotes(entryCommentView.getText().toString());
|
||||
newEntry.setPassword(entryPasswordView.getText().toString());
|
||||
|
||||
if (newEntry.allowExtraFields()) {
|
||||
// Delete all new standard strings
|
||||
@@ -307,6 +283,8 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
}
|
||||
|
||||
newEntry.endToDecodeReference(db);
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
@@ -334,11 +312,9 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
protected void fillData() {
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
ImageButton currIconButton = findViewById(R.id.icon_button);
|
||||
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
|
||||
|
||||
boolean visibilityFont = PreferencesUtil.fieldFontIsInVisibility(this);
|
||||
|
||||
entryTitleView.setText(mEntry.getTitle());
|
||||
entryUserNameView.setText(mEntry.getUsername());
|
||||
entryUrlView.setText(mEntry.getUrl());
|
||||
@@ -346,14 +322,21 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
entryPasswordView.setText(password);
|
||||
entryConfirmationPasswordView.setText(password);
|
||||
entryCommentView.setText(mEntry.getNotes());
|
||||
Util.applyFontVisibilityToTextView(visibilityFont, entryCommentView);
|
||||
|
||||
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
|
||||
if (visibilityFontActivated) {
|
||||
Util.applyFontVisibilityTo(entryUserNameView);
|
||||
Util.applyFontVisibilityTo(entryPasswordView);
|
||||
Util.applyFontVisibilityTo(entryConfirmationPasswordView);
|
||||
Util.applyFontVisibilityTo(entryCommentView);
|
||||
}
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
LinearLayout container = findViewById(R.id.advanced_container);
|
||||
for (Map.Entry<String, ProtectedString> pair : mEntry.getExtraProtectedFields().entrySet()) {
|
||||
EntryEditNewField entryEditNewField = new EntryEditNewField(EntryEditActivity.this);
|
||||
entryEditNewField.setData(pair.getKey(), pair.getValue());
|
||||
entryEditNewField.setFontVisibility(visibilityFont);
|
||||
entryEditNewField.setFontVisibility(visibilityFontActivated);
|
||||
container.addView(entryEditNewField);
|
||||
}
|
||||
}
|
||||
@@ -362,7 +345,7 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
ImageButton currIconButton = findViewById(R.id.icon_button);
|
||||
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
|
||||
}
|
||||
|
||||
@@ -382,12 +365,13 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
public void finish() {
|
||||
// Assign entry callback as a result in all case
|
||||
if (mCallbackNewEntry != null) {
|
||||
Bundle bundle = new Bundle();
|
||||
Intent intentEntry = new Intent();
|
||||
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||
intentEntry.putExtras(bundle);
|
||||
if (mIsNew) {
|
||||
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
|
||||
} else {
|
||||
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.keepassdroid.adapters.NodeAdapter;
|
||||
@@ -54,25 +53,27 @@ import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.keepassdroid.database.edit.AddGroup;
|
||||
import com.keepassdroid.database.edit.DeleteEntry;
|
||||
import com.keepassdroid.database.edit.DeleteGroup;
|
||||
import com.keepassdroid.dialog.ReadOnlyDialog;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.GroupEditDialogFragment;
|
||||
import com.keepassdroid.fragments.IconPickerDialogFragment;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.dialogs.GroupEditDialogFragment;
|
||||
import com.keepassdroid.dialogs.IconPickerDialogFragment;
|
||||
import com.keepassdroid.dialogs.ReadOnlyDialog;
|
||||
import com.keepassdroid.search.SearchResultsActivity;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.view.ListNodesWithAddButtonView;
|
||||
import com.keepassdroid.view.AddNodeButtonView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class GroupActivity extends ListNodesActivity
|
||||
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
|
||||
|
||||
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||
|
||||
private AddNodeButtonView addNodeButtonView;
|
||||
|
||||
protected boolean addGroupEnabled = false;
|
||||
protected boolean addEntryEnabled = false;
|
||||
protected boolean isRoot = false;
|
||||
protected boolean readOnly = false;
|
||||
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
private ListNodesWithAddButtonView rootView;
|
||||
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
@@ -83,31 +84,41 @@ public class GroupActivity extends ListNodesActivity
|
||||
private static final String TAG = "Group Activity:";
|
||||
|
||||
public static void launch(Activity act) {
|
||||
launch(act, (PwGroup) null);
|
||||
LockingActivity.recordFirstTimeBeforeLaunch(act);
|
||||
launch(act, (PwGroup) null);
|
||||
}
|
||||
|
||||
public static void launch(Activity act, PwGroup group) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if ( group != null ) {
|
||||
intent.putExtra(KEY_ENTRY, group.getId());
|
||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if (group != null) {
|
||||
intent.putExtra(GROUP_ID_KEY, group.getId());
|
||||
}
|
||||
act.startActivityForResult(intent, 0);
|
||||
}
|
||||
act.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(Activity act, AssistStructure assistStructure) {
|
||||
launch(act, null, assistStructure);
|
||||
if ( assistStructure != null ) {
|
||||
LockingActivity.recordFirstTimeBeforeLaunch(act);
|
||||
launch(act, null, assistStructure);
|
||||
} else {
|
||||
launch(act);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) {
|
||||
if ( assistStructure != null ) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if ( group != null ) {
|
||||
intent.putExtra(KEY_ENTRY, group.getId());
|
||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if (group != null) {
|
||||
intent.putExtra(GROUP_ID_KEY, group.getId());
|
||||
}
|
||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||
}
|
||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||
} else {
|
||||
launch(act, group);
|
||||
}
|
||||
@@ -124,31 +135,30 @@ public class GroupActivity extends ListNodesActivity
|
||||
}
|
||||
|
||||
// Construct main view
|
||||
rootView = new ListNodesWithAddButtonView(this);
|
||||
rootView.enableAddGroup(addGroupEnabled);
|
||||
rootView.enableAddEntry(addEntryEnabled);
|
||||
setContentView(rootView);
|
||||
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
addNodeButtonView = findViewById(R.id.add_node_button);
|
||||
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
||||
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
||||
// Hide when scroll
|
||||
RecyclerView recyclerView = findViewById(R.id.nodes_list);
|
||||
recyclerView.addOnScrollListener(addNodeButtonView.hideButtonOnScrollListener());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if ( mCurrentGroup.getParent() != null )
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
||||
|
||||
rootView.setAddGroupClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
editGroupDialogAction = EditGroupDialogAction.CREATION;
|
||||
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
}
|
||||
});
|
||||
rootView.setAddEntryClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup);
|
||||
}
|
||||
addNodeButtonView.setAddGroupClickListener(v -> {
|
||||
editGroupDialogAction = EditGroupDialogAction.CREATION;
|
||||
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
});
|
||||
addNodeButtonView.setAddEntryClickListener(v ->
|
||||
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup));
|
||||
|
||||
setGroupTitle();
|
||||
setGroupIcon();
|
||||
@@ -172,19 +182,20 @@ public class GroupActivity extends ListNodesActivity
|
||||
PwGroup root = db.pm.rootGroup;
|
||||
|
||||
Log.w(TAG, "Creating tree view");
|
||||
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(KEY_ENTRY);
|
||||
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
|
||||
if ( pwGroupId == null ) {
|
||||
currentGroup = root;
|
||||
} else {
|
||||
currentGroup = db.pm.groups.get(pwGroupId);
|
||||
}
|
||||
|
||||
addGroupEnabled = !readOnly;
|
||||
addEntryEnabled = !readOnly;
|
||||
|
||||
isRoot = (currentGroup == root);
|
||||
if ( !currentGroup.allowAddEntryIfIsRoot() )
|
||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||
if (currentGroup != null) {
|
||||
addGroupEnabled = !readOnly;
|
||||
addEntryEnabled = !readOnly; // TODO ReadOnly
|
||||
isRoot = (currentGroup == root);
|
||||
if (!currentGroup.allowAddEntryIfIsRoot())
|
||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||
}
|
||||
|
||||
return currentGroup;
|
||||
}
|
||||
@@ -276,7 +287,14 @@ public class GroupActivity extends ListNodesActivity
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Show button on resume
|
||||
rootView.showButton();
|
||||
addNodeButtonView.showButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
// Hide button
|
||||
addNodeButtonView.hideButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -291,9 +309,8 @@ public class GroupActivity extends ListNodesActivity
|
||||
@Override
|
||||
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
||||
super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
|
||||
|
||||
// Show button if hide after sort
|
||||
rootView.showButton();
|
||||
addNodeButtonView.showButton();
|
||||
}
|
||||
|
||||
protected void setGroupIcon() {
|
||||
@@ -339,6 +356,7 @@ 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.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||
}
|
||||
@@ -359,9 +377,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
App.setShutdown();
|
||||
setResult(PasswordActivity.RESULT_EXIT_LOCK);
|
||||
finish();
|
||||
lockAndExit();
|
||||
return true;
|
||||
|
||||
case R.id.menu_change_master_key:
|
||||
|
||||
@@ -38,7 +38,6 @@ import android.widget.TextView;
|
||||
|
||||
import com.keepassdroid.adapters.NodeAdapter;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ActivityCompat;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
@@ -46,13 +45,13 @@ import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwNode;
|
||||
import com.keepassdroid.database.edit.AfterAddNodeOnFinish;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.SortDialogFragment;
|
||||
import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.dialogs.SortDialogFragment;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.tasks.UIToastTask;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.keepassdroid.view.AssignPasswordHelper;
|
||||
import com.keepassdroid.password.AssignPasswordHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public abstract class ListNodesActivity extends LockingActivity
|
||||
@@ -60,8 +59,6 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
NodeAdapter.OnNodeClickCallback,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
|
||||
protected PwGroup mCurrentGroup;
|
||||
protected NodeAdapter mAdapter;
|
||||
|
||||
@@ -83,8 +80,9 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
invalidateOptionsMenu();
|
||||
|
||||
// TODO Move in search
|
||||
setContentView(R.layout.list_nodes);
|
||||
|
||||
mCurrentGroup = initCurrentGroup();
|
||||
@@ -202,7 +200,8 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
return true;
|
||||
|
||||
default:
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
|
||||
// Check the time lock before launching settings
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -262,6 +261,7 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// TODO BUG HERE
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
}
|
||||
@@ -299,6 +299,7 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
PwGroup recycleBin = database.getRecycleBin();
|
||||
// Add trash if it doesn't exists
|
||||
if (parent.equals(recycleBin)
|
||||
&& mCurrentGroup != null
|
||||
&& mCurrentGroup.getParent() == null
|
||||
&& !mCurrentGroup.equals(recycleBin)) {
|
||||
mAdapter.addNode(parent);
|
||||
|
||||
@@ -19,47 +19,95 @@
|
||||
*/
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.keepassdroid.timeout.TimeoutHelper;
|
||||
|
||||
|
||||
public abstract class LockingActivity extends StylishActivity {
|
||||
|
||||
private static final String TAG = LockingActivity.class.getName();
|
||||
|
||||
public static final int RESULT_EXIT_LOCK = 1450;
|
||||
|
||||
private static final String AT_LEAST_SECOND_SHOWN_KEY = "AT_LEAST_SECOND_SHOWN_KEY";
|
||||
|
||||
private ScreenReceiver screenReceiver;
|
||||
private boolean exitLock;
|
||||
|
||||
protected static void recordFirstTimeBeforeLaunch(Activity activity) {
|
||||
TimeoutHelper.recordTime(activity);
|
||||
}
|
||||
|
||||
protected static boolean checkTimeIsAllowedOrFinish(Activity activity) {
|
||||
return TimeoutHelper.checkTime(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this)) {
|
||||
screenReceiver = new ScreenReceiver();
|
||||
registerReceiver(screenReceiver, new IntentFilter((Intent.ACTION_SCREEN_OFF)));
|
||||
} else
|
||||
screenReceiver = null;
|
||||
|
||||
exitLock = false;
|
||||
|
||||
// WARNING TODO recordTime is not called after a back if was in backstack
|
||||
}
|
||||
|
||||
public static void checkShutdown(Activity act) {
|
||||
if (App.isShutdown() && App.getDB().Loaded()) {
|
||||
Log.i(TAG, "Shutdown " + act.getLocalClassName() +
|
||||
" after inactivity or manual lock");
|
||||
act.setResult(RESULT_EXIT_LOCK);
|
||||
act.finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected void lockAndExit() {
|
||||
App.setShutdown();
|
||||
setResult(LockingActivity.RESULT_EXIT_LOCK);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_EXIT_LOCK) {
|
||||
exitLock = true;
|
||||
checkShutdown(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
TimeoutHelper.checkShutdown(this);
|
||||
TimeoutHelper.resume(this);
|
||||
// After the first creation
|
||||
// or If simply swipe with another application
|
||||
// If the time is out -> close the Activity
|
||||
TimeoutHelper.checkTime(this);
|
||||
// If onCreate already record time
|
||||
if (!exitLock)
|
||||
TimeoutHelper.recordTime(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
TimeoutHelper.pause(this);
|
||||
// If the time is out during our navigation in activity -> close the Activity
|
||||
TimeoutHelper.checkTime(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +117,12 @@ public abstract class LockingActivity extends StylishActivity {
|
||||
unregisterReceiver(screenReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(AT_LEAST_SECOND_SHOWN_KEY, true);
|
||||
}
|
||||
|
||||
public class ScreenReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@@ -78,7 +132,7 @@ public abstract class LockingActivity extends StylishActivity {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) {
|
||||
App.setShutdown();
|
||||
TimeoutHelper.checkShutdown(LockingActivity.this);
|
||||
checkShutdown(LockingActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,9 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
*/
|
||||
public void rebuildList(PwGroup group) {
|
||||
this.nodeSortedList.clear();
|
||||
this.nodeSortedList.addAll(group.getDirectChildren());
|
||||
if (group != null) {
|
||||
this.nodeSortedList.addAll(group.getDirectChildren());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.Calendar;
|
||||
public class App extends MultiDexApplication {
|
||||
private static Database db = null;
|
||||
private static boolean shutdown = false;
|
||||
private static CharSequence mMessage = "";
|
||||
private static Calendar calendar = null;
|
||||
private static RecentFileHistory fileHistory = null;
|
||||
|
||||
@@ -38,7 +39,6 @@ public class App extends MultiDexApplication {
|
||||
if ( db == null ) {
|
||||
db = new Database();
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -56,17 +56,27 @@ public class App extends MultiDexApplication {
|
||||
|
||||
public static void setShutdown() {
|
||||
shutdown = true;
|
||||
mMessage = "";
|
||||
}
|
||||
|
||||
public static void setShutdown(CharSequence message) {
|
||||
shutdown = true;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public static CharSequence getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public static void clearShutdown() {
|
||||
shutdown = false;
|
||||
mMessage = "";
|
||||
}
|
||||
|
||||
public static Calendar getCalendar() {
|
||||
if ( calendar == null ) {
|
||||
calendar = Calendar.getInstance();
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
@@ -75,9 +85,7 @@ public class App extends MultiDexApplication {
|
||||
super.onCreate();
|
||||
|
||||
Stylish.init(this);
|
||||
|
||||
fileHistory = new RecentFileHistory(this);
|
||||
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
|
||||
@@ -86,7 +94,6 @@ public class App extends MultiDexApplication {
|
||||
if ( db != null ) {
|
||||
db.clear();
|
||||
}
|
||||
|
||||
super.onTerminate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.compat;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class ActivityCompat {
|
||||
private static Method invalidateOptMenu;
|
||||
|
||||
static {
|
||||
try {
|
||||
invalidateOptMenu = Activity.class.getMethod("invalidateOptionsMenu", (Class<Activity>[]) null);
|
||||
} catch (Exception e) {
|
||||
// Do nothing if method dosen't exist
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateOptionsMenu(Activity act) {
|
||||
if (invalidateOptMenu != null) {
|
||||
try {
|
||||
invalidateOptMenu.invoke(act, (Object[]) null);
|
||||
} catch (Exception e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
app/src/main/java/com/keepassdroid/database/AutoType.java
Normal file
37
app/src/main/java/com/keepassdroid/database/AutoType.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class AutoType implements Cloneable, Serializable {
|
||||
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<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AutoType clone() {
|
||||
AutoType auto;
|
||||
try {
|
||||
auto = (AutoType) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
auto.windowSeqPairs = (HashMap<String, String>) windowSeqPairs.clone();
|
||||
return auto;
|
||||
}
|
||||
|
||||
public void put(String key, String value) {
|
||||
windowSeqPairs.put(key, value);
|
||||
}
|
||||
|
||||
public Set<Map.Entry<String, String>> entrySet() {
|
||||
return windowSeqPairs.entrySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import java.util.Set;
|
||||
import com.keepassdroid.database.security.ProtectedBinary;
|
||||
|
||||
public class BinaryPool {
|
||||
private HashMap<Integer, ProtectedBinary> pool = new HashMap<Integer, ProtectedBinary>();
|
||||
private HashMap<Integer, ProtectedBinary> pool = new HashMap<>();
|
||||
|
||||
public BinaryPool() {
|
||||
|
||||
@@ -63,12 +63,10 @@ public class BinaryPool {
|
||||
|
||||
@Override
|
||||
public boolean operate(PwEntryV4 entry) {
|
||||
for (PwEntryV4 histEntry : entry.history) {
|
||||
poolAdd(histEntry.binaries);
|
||||
|
||||
for (PwEntryV4 histEntry : entry.getHistory()) {
|
||||
poolAdd(histEntry.getBinaries());
|
||||
}
|
||||
|
||||
poolAdd(entry.binaries);
|
||||
poolAdd(entry.getBinaries());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class EntrySearchHandler extends EntryHandler<PwEntry> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) {
|
||||
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public class EntrySearchHandlerAll extends EntryHandler<PwEntry> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) {
|
||||
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class EntrySearchHandlerV4 extends EntrySearchHandler {
|
||||
protected boolean searchID(PwEntry e) {
|
||||
PwEntryV4 entry = (PwEntryV4) e;
|
||||
if (sp.searchInUUIDs) {
|
||||
String hex = UuidUtil.toHexString(entry.uuid);
|
||||
String hex = UuidUtil.toHexString(entry.getUUID());
|
||||
return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
public interface ISmallTimeLogger {
|
||||
|
||||
PwDate getLastModificationTime();
|
||||
void setLastModificationTime(PwDate date);
|
||||
|
||||
PwDate getCreationTime();
|
||||
void setCreationTime(PwDate date);
|
||||
|
||||
PwDate getLastAccessTime();
|
||||
void setLastAccessTime(PwDate date);
|
||||
|
||||
PwDate getExpiryTime();
|
||||
void setExpiryTime(PwDate date);
|
||||
|
||||
boolean expires();
|
||||
void setExpires(boolean exp);
|
||||
}
|
||||
@@ -19,28 +19,12 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.util.Date;
|
||||
public interface ITimeLogger extends ISmallTimeLogger {
|
||||
|
||||
public interface ITimeLogger {
|
||||
Date getLastModificationTime();
|
||||
void setLastModificationTime(Date date);
|
||||
|
||||
Date getCreationTime();
|
||||
void setCreationTime(Date date);
|
||||
|
||||
Date getLastAccessTime();
|
||||
void setLastAccessTime(Date date);
|
||||
|
||||
Date getExpiryTime();
|
||||
void setExpiryTime(Date date);
|
||||
|
||||
boolean expires();
|
||||
void setExpires(boolean exp);
|
||||
|
||||
long getUsageCount();
|
||||
void setUsageCount(long count);
|
||||
|
||||
Date getLocationChanged();
|
||||
void setLocationChanged(Date date);
|
||||
|
||||
PwDate getLocationChanged();
|
||||
void setLocationChanged(PwDate date);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,13 +41,14 @@ import com.keepassdroid.utils.Util;
|
||||
|
||||
public abstract class PwDatabase {
|
||||
|
||||
public static final UUID UUID_ZERO = new UUID(0,0);
|
||||
public byte masterKey[] = new byte[32];
|
||||
public byte[] finalKey;
|
||||
public String name = "KeePass database";
|
||||
public PwGroup rootGroup;
|
||||
public PwIconFactory iconFactory = new PwIconFactory();
|
||||
public Map<PwGroupId, PwGroup> groups = new HashMap<PwGroupId, PwGroup>();
|
||||
public Map<UUID, PwEntry> entries = new HashMap<UUID, PwEntry>();
|
||||
public Map<PwGroupId, PwGroup> groups = new HashMap<>();
|
||||
public Map<UUID, PwEntry> entries = new HashMap<>();
|
||||
|
||||
|
||||
private static boolean isKDBExtension(String filename) {
|
||||
@@ -317,8 +318,8 @@ public abstract class PwDatabase {
|
||||
|
||||
public void populateGlobals(PwGroup currentGroup) {
|
||||
|
||||
List<PwGroup> childGroups = currentGroup.childGroups;
|
||||
List<PwEntry> childEntries = currentGroup.childEntries;
|
||||
List<PwGroup> childGroups = currentGroup.getChildGroups();
|
||||
List<PwEntry> childEntries = currentGroup.getChildEntries();
|
||||
|
||||
for (int i = 0; i < childEntries.size(); i++ ) {
|
||||
PwEntry cur = childEntries.get(i);
|
||||
|
||||
@@ -69,9 +69,9 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
public PwEntry metaInfo;
|
||||
|
||||
// all entries
|
||||
public List<PwEntry> entries = new ArrayList<PwEntry>();
|
||||
public List<PwEntry> entries = new ArrayList<>();
|
||||
// all groups
|
||||
public List<PwGroup> groups = new ArrayList<PwGroup>();
|
||||
public List<PwGroup> groups = new ArrayList<>();
|
||||
// Algorithm used to encrypt the database
|
||||
public PwEncryptionAlgorithm algorithm;
|
||||
public int numKeyEncRounds;
|
||||
@@ -105,7 +105,7 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
List<PwGroup> kids = new ArrayList<PwGroup>();
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) groups.get(i);
|
||||
if (grp.level == target)
|
||||
if (grp.getLevel() == target)
|
||||
kids.add(grp);
|
||||
}
|
||||
return kids;
|
||||
@@ -114,8 +114,8 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
public int getRootGroupId() {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) groups.get(i);
|
||||
if (grp.level == 0) {
|
||||
return grp.groupId;
|
||||
if (grp.getLevel() == 0) {
|
||||
return grp.getGroupId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,13 +124,13 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
|
||||
public List<PwGroup> getGrpChildren(PwGroupV3 parent) {
|
||||
int idx = groups.indexOf(parent);
|
||||
int target = parent.level + 1;
|
||||
int target = parent.getLevel() + 1;
|
||||
List<PwGroup> kids = new ArrayList<PwGroup>();
|
||||
while (++idx < groups.size()) {
|
||||
PwGroupV3 grp = (PwGroupV3) groups.get(idx);
|
||||
if (grp.level < target)
|
||||
if (grp.getLevel() < target)
|
||||
break;
|
||||
else if (grp.level == target)
|
||||
else if (grp.getLevel() == target)
|
||||
kids.add(grp);
|
||||
}
|
||||
return kids;
|
||||
@@ -145,7 +145,7 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
*/
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
PwEntryV3 ent = (PwEntryV3) entries.get(i);
|
||||
if (ent.groupId == parent.groupId)
|
||||
if (ent.getGroupId() == parent.getGroupId())
|
||||
kids.add(ent);
|
||||
}
|
||||
return kids;
|
||||
@@ -163,11 +163,11 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
|
||||
List<PwGroup> rootChildGroups = getGrpRoots();
|
||||
root.setGroups(rootChildGroups);
|
||||
root.childEntries = new ArrayList<>();
|
||||
root.level = -1;
|
||||
root.setEntries(new ArrayList<>());
|
||||
root.setLevel(-1);
|
||||
for (int i = 0; i < rootChildGroups.size(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i);
|
||||
grp.parent = root;
|
||||
grp.setParent(root);
|
||||
constructTree(grp);
|
||||
}
|
||||
return;
|
||||
@@ -176,18 +176,18 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
// I'm in non-root
|
||||
// get child groups
|
||||
currentGroup.setGroups(getGrpChildren(currentGroup));
|
||||
currentGroup.childEntries = getEntries(currentGroup);
|
||||
currentGroup.setEntries(getEntries(currentGroup));
|
||||
|
||||
// set parent in child entries
|
||||
for (int i = 0; i < currentGroup.numbersOfChildEntries(); i++) {
|
||||
PwEntryV3 entry = (PwEntryV3) currentGroup.childEntries.get(i);
|
||||
entry.parent = currentGroup;
|
||||
PwEntryV3 entry = (PwEntryV3) currentGroup.getChildEntryAt(i);
|
||||
entry.setParent(currentGroup);
|
||||
}
|
||||
// recursively construct child groups
|
||||
for (int i = 0; i < currentGroup.numbersOfChildGroups(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) currentGroup.childGroups.get(i);
|
||||
grp.parent = currentGroup;
|
||||
constructTree((PwGroupV3) currentGroup.childGroups.get(i));
|
||||
PwGroupV3 grp = (PwGroupV3) currentGroup.getChildGroupAt(i);
|
||||
grp.setParent(currentGroup);
|
||||
constructTree((PwGroupV3) currentGroup.getChildGroupAt(i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -221,18 +221,16 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
|
||||
public byte[] getMasterKey(String key, InputStream keyInputStream)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
assert (key != null);
|
||||
|
||||
if (key.length() > 0 && keyInputStream != null) {
|
||||
if (key != null && key.length() > 0 && keyInputStream != null) {
|
||||
return getCompositeKey(key, keyInputStream);
|
||||
} else if (key.length() > 0) {
|
||||
} else if (key != null && key.length() > 0) {
|
||||
return getPasswordKey(key);
|
||||
} else if (keyInputStream != null) {
|
||||
return getFileKey(keyInputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key cannot be empty.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -318,11 +316,11 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
public boolean isBackup(PwGroup group) {
|
||||
PwGroupV3 g = (PwGroupV3) group;
|
||||
while (g != null) {
|
||||
if (g.level == 0 && g.name.equalsIgnoreCase("Backup")) {
|
||||
if (g.getLevel() == 0 && g.getName().equalsIgnoreCase("Backup")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
g = g.parent;
|
||||
g = (PwGroupV3) g.getParent();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -340,7 +338,7 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
private void initAndAddGroup(String name, int iconId, PwGroup parent) {
|
||||
PwGroup group = createGroup();
|
||||
group.initNewGroup(name, newGroupId());
|
||||
group.icon = iconFactory.getIcon(iconId);
|
||||
group.setIcon(iconFactory.getIcon(iconId));
|
||||
addGroupTo(group, parent);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,6 @@ import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
public class PwDatabaseV4 extends PwDatabase {
|
||||
|
||||
public static final Date DEFAULT_NOW = new Date();
|
||||
public static final UUID UUID_ZERO = new UUID(0,0);
|
||||
public static final int DEFAULT_ROUNDS = 6000;
|
||||
private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited
|
||||
private static final long DEFAULT_HISTORY_MAX_SIZE = 6 * 1024 * 1024; // -1 unlimited
|
||||
@@ -71,14 +69,14 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
|
||||
// TODO: Refactor me away to get directly from kdfParameters
|
||||
public long numKeyEncRounds = 6000;
|
||||
public Date nameChanged = DEFAULT_NOW;
|
||||
public Date settingsChanged = DEFAULT_NOW;
|
||||
public Date nameChanged = new Date();
|
||||
public Date settingsChanged = new Date();
|
||||
public String description = "";
|
||||
public Date descriptionChanged = DEFAULT_NOW;
|
||||
public Date descriptionChanged = new Date();
|
||||
public String defaultUserName = "";
|
||||
public Date defaultUserNameChanged = DEFAULT_NOW;
|
||||
public Date defaultUserNameChanged = new Date();
|
||||
|
||||
public Date keyLastChanged = DEFAULT_NOW;
|
||||
public Date keyLastChanged = new Date();
|
||||
public long keyChangeRecDays = -1;
|
||||
public long keyChangeForceDays = 1;
|
||||
public boolean keyChangeForceOnce = false;
|
||||
@@ -87,17 +85,17 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
public String color = "";
|
||||
public boolean recycleBinEnabled = true;
|
||||
public UUID recycleBinUUID = UUID_ZERO;
|
||||
public Date recycleBinChanged = DEFAULT_NOW;
|
||||
public Date recycleBinChanged = new Date();
|
||||
public UUID entryTemplatesGroup = UUID_ZERO;
|
||||
public Date entryTemplatesGroupChanged = DEFAULT_NOW;
|
||||
public Date entryTemplatesGroupChanged = new Date();
|
||||
public int historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS;
|
||||
public long historyMaxSize = DEFAULT_HISTORY_MAX_SIZE;
|
||||
public UUID lastSelectedGroup = UUID_ZERO;
|
||||
public UUID lastTopVisibleGroup = UUID_ZERO;
|
||||
public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig();
|
||||
public List<PwDeletedObject> deletedObjects = new ArrayList<PwDeletedObject>();
|
||||
public List<PwIconCustom> customIcons = new ArrayList<PwIconCustom>();
|
||||
public Map<String, String> customData = new HashMap<String, String>();
|
||||
public List<PwDeletedObject> deletedObjects = new ArrayList<>();
|
||||
public List<PwIconCustom> customIcons = new ArrayList<>();
|
||||
public Map<String, String> customData = new HashMap<>();
|
||||
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
|
||||
public VariantDictionary publicCustomData = new VariantDictionary();
|
||||
public BinaryPool binPool = new BinaryPool();
|
||||
@@ -269,25 +267,42 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
public List<PwGroup> getGroups() {
|
||||
List<PwGroup> list = new ArrayList<PwGroup>();
|
||||
PwGroupV4 root = (PwGroupV4) rootGroup;
|
||||
root.buildChildGroupsRecursive(list);
|
||||
buildChildGroupsRecursive(root, list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void buildChildGroupsRecursive(PwGroupV4 root, List<PwGroup> list) {
|
||||
list.add(root);
|
||||
for ( int i = 0; i < root.numbersOfChildGroups(); i++) {
|
||||
PwGroupV4 child = (PwGroupV4) root.getChildGroupAt(i);
|
||||
buildChildGroupsRecursive(child, list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PwGroup> getGrpRoots() {
|
||||
return rootGroup.childGroups;
|
||||
return rootGroup.getChildGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PwEntry> getEntries() {
|
||||
List<PwEntry> list = new ArrayList<PwEntry>();
|
||||
PwGroupV4 root = (PwGroupV4) rootGroup;
|
||||
root.buildChildEntriesRecursive(list);
|
||||
|
||||
buildChildEntriesRecursive(root, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void buildChildEntriesRecursive(PwGroupV4 root, List<PwEntry> list) {
|
||||
for ( int i = 0; i < root.numbersOfChildEntries(); i++ ) {
|
||||
list.add(root.getChildEntryAt(i));
|
||||
}
|
||||
for ( int i = 0; i < root.numbersOfChildGroups(); i++ ) {
|
||||
PwGroupV4 child = (PwGroupV4) root.getChildGroupAt(i);
|
||||
buildChildEntriesRecursive(child, list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumRounds() {
|
||||
return numKeyEncRounds;
|
||||
@@ -351,13 +366,13 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
if (getRecycleBin() == null) {
|
||||
// Create recycle bin
|
||||
|
||||
PwGroupV4 recycleBin = new PwGroupV4(true, true, RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN));
|
||||
recycleBin.enableAutoType = false;
|
||||
recycleBin.enableSearching = false;
|
||||
recycleBin.isExpanded = false;
|
||||
PwGroupV4 recycleBin = new PwGroupV4(RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN));
|
||||
recycleBin.setEnableAutoType(false);
|
||||
recycleBin.setEnableSearching(false);
|
||||
recycleBin.setExpanded(false);
|
||||
addGroupTo(recycleBin, rootGroup);
|
||||
|
||||
recycleBinUUID = recycleBin.uuid;
|
||||
recycleBinUUID = recycleBin.getUUID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,7 +495,7 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
public void initNew(String dbPath) {
|
||||
String filename = URLUtil.guessFileName(dbPath, null, null);
|
||||
|
||||
rootGroup = new PwGroupV4(true, true, dbNameFromPath(dbPath), iconFactory.getIcon(PwIconStandard.FOLDER));
|
||||
rootGroup = new PwGroupV4(dbNameFromPath(dbPath), iconFactory.getIcon(PwIconStandard.FOLDER));
|
||||
groups.put(rootGroup.getId(), rootGroup);
|
||||
}
|
||||
|
||||
@@ -508,7 +523,7 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
return true;
|
||||
}
|
||||
PwGroupV4 g4 = (PwGroupV4) group;
|
||||
if (g4.customData.size() > 0) {
|
||||
if (g4.containsCustomData()) {
|
||||
hasCustomData = true;
|
||||
return false;
|
||||
}
|
||||
@@ -528,7 +543,7 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
}
|
||||
|
||||
PwEntryV4 e4 = (PwEntryV4)entry;
|
||||
if (e4.customData.size() > 0) {
|
||||
if (e4.containsCustomData()) {
|
||||
hasCustomData = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
/** Converting from the C Date format to the Java data format is
|
||||
* expensive when done for every record at once. I use this class to
|
||||
* allow lazy conversions between the formats.
|
||||
@@ -34,7 +34,7 @@ import com.keepassdroid.utils.Types;
|
||||
*
|
||||
*/
|
||||
public class PwDate implements Cloneable, Serializable {
|
||||
|
||||
|
||||
private static final int DATE_SIZE = 5;
|
||||
|
||||
private boolean cDateBuilt = false;
|
||||
@@ -42,6 +42,56 @@ public class PwDate implements Cloneable, Serializable {
|
||||
|
||||
private Date jDate;
|
||||
private byte[] cDate;
|
||||
|
||||
public static final Date NEVER_EXPIRE = getNeverExpire();
|
||||
public static final Date NEVER_EXPIRE_BUG = getNeverExpireBug();
|
||||
|
||||
public static final Date DEFAULT_DATE = getDefaultDate();
|
||||
public static final PwDate PW_NEVER_EXPIRE = new PwDate(NEVER_EXPIRE);
|
||||
public static final PwDate PW_NEVER_EXPIRE_BUG = new PwDate(NEVER_EXPIRE_BUG);
|
||||
public static final PwDate DEFAULT_PWDATE = new PwDate(DEFAULT_DATE);
|
||||
|
||||
private static Date getDefaultDate() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2004);
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
cal.set(Calendar.HOUR, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
private static Date getNeverExpire() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2999);
|
||||
cal.set(Calendar.MONTH, 11);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 28);
|
||||
cal.set(Calendar.HOUR, 23);
|
||||
cal.set(Calendar.MINUTE, 59);
|
||||
cal.set(Calendar.SECOND, 59);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
/** This date was was accidentally being written
|
||||
* out when an entry was supposed to be marked as
|
||||
* expired. We'll use this to silently correct those
|
||||
* entries.
|
||||
* @return
|
||||
*/
|
||||
private static Date getNeverExpireBug() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2999);
|
||||
cal.set(Calendar.MONTH, 11);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 30);
|
||||
cal.set(Calendar.HOUR, 23);
|
||||
cal.set(Calendar.MINUTE, 59);
|
||||
cal.set(Calendar.SECOND, 59);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public PwDate(byte[] buf, int offset) {
|
||||
cDate = new byte[DATE_SIZE];
|
||||
@@ -59,12 +109,13 @@ public class PwDate implements Cloneable, Serializable {
|
||||
jDateBuilt = true;
|
||||
}
|
||||
|
||||
private PwDate() {
|
||||
|
||||
public PwDate() {
|
||||
jDate = new Date();
|
||||
jDateBuilt = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
public PwDate clone() {
|
||||
PwDate copy = new PwDate();
|
||||
|
||||
if ( cDateBuilt ) {
|
||||
@@ -82,9 +133,7 @@ public class PwDate implements Cloneable, Serializable {
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Date getJDate() {
|
||||
public Date getDate() {
|
||||
if ( ! jDateBuilt ) {
|
||||
jDate = readTime(cDate, 0, App.getCalendar());
|
||||
jDateBuilt = true;
|
||||
@@ -102,7 +151,6 @@ public class PwDate implements Cloneable, Serializable {
|
||||
return cDate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
|
||||
* to a java.util.Date instance.
|
||||
@@ -189,7 +237,7 @@ public class PwDate implements Cloneable, Serializable {
|
||||
} else if ( cDateBuilt && date.jDateBuilt ) {
|
||||
return Arrays.equals(date.getCDate(), cDate);
|
||||
} else {
|
||||
return IsSameDate(date.getJDate(), jDate);
|
||||
return IsSameDate(date.getDate(), jDate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,22 @@ package com.keepassdroid.database;
|
||||
import com.keepassdroid.database.iterator.EntrySearchStringIterator;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
|
||||
protected static final String PMS_TAN_ENTRY = "<TAN>";
|
||||
|
||||
public PwIconStandard icon = PwIconStandard.FIRST;
|
||||
|
||||
private static final String PMS_TAN_ENTRY = "<TAN>";
|
||||
|
||||
protected UUID uuid = PwDatabase.UUID_ZERO;
|
||||
|
||||
@Override
|
||||
protected void construct() {
|
||||
super.construct();
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
public static PwEntry getInstance(PwGroup parent) {
|
||||
if (parent instanceof PwGroupV3) {
|
||||
return new PwEntryV3((PwGroupV3)parent);
|
||||
@@ -45,23 +49,23 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
throw new RuntimeException("Unknown PwGroup instance.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
public PwEntry clone() {
|
||||
PwEntry newEntry;
|
||||
try {
|
||||
newEntry = (PwEntry) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert(false);
|
||||
throw new RuntimeException("Clone should be supported");
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
public PwEntry clone(boolean deepStrings) {
|
||||
return (PwEntry) clone();
|
||||
}
|
||||
@Override
|
||||
protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
|
||||
super.addCloneAttributesToNewEntry(newEntry);
|
||||
// uuid is clone automatically (IMMUTABLE)
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
@@ -69,65 +73,41 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
}
|
||||
|
||||
public void assign(PwEntry source) {
|
||||
icon = source.icon;
|
||||
}
|
||||
|
||||
public abstract UUID getUUID();
|
||||
public abstract void setUUID(UUID u);
|
||||
|
||||
public String getTitle() {
|
||||
return getTitle(false, null);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return getUsername(false, null);
|
||||
super.assign(source);
|
||||
uuid = source.uuid;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return getPassword(false, null);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return getUrl(false, null);
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return getNotes(false, null);
|
||||
}
|
||||
|
||||
public abstract String getTitle(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getUsername(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getPassword(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getUrl(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getNotes(boolean decodeRef, PwDatabase db);
|
||||
public abstract Date getLastModificationTime();
|
||||
public abstract Date getLastAccessTime();
|
||||
public abstract Date getExpiryTime();
|
||||
public abstract boolean expires();
|
||||
|
||||
public abstract void setTitle(String title, PwDatabase db);
|
||||
public abstract void setUsername(String user, PwDatabase db);
|
||||
public abstract void setPassword(String pass, PwDatabase db);
|
||||
public abstract void setUrl(String url, PwDatabase db);
|
||||
public abstract void setNotes(String notes, PwDatabase db);
|
||||
public abstract void setCreationTime(Date create);
|
||||
public abstract void setLastModificationTime(Date mod);
|
||||
public abstract void setLastAccessTime(Date access);
|
||||
public abstract void setExpires(boolean exp);
|
||||
public abstract void setExpiryTime(Date expires);
|
||||
|
||||
public PwIcon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(PwIconStandard icon) {
|
||||
this.icon = icon;
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUUID(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public void startToDecodeReference(PwDatabase db) {}
|
||||
public void endToDecodeReference(PwDatabase db) {}
|
||||
|
||||
public abstract String getTitle();
|
||||
public abstract void setTitle(String title);
|
||||
|
||||
public abstract String getUsername();
|
||||
public abstract void setUsername(String user);
|
||||
|
||||
public abstract String getPassword();
|
||||
public abstract void setPassword(String pass);
|
||||
|
||||
public abstract String getUrl();
|
||||
public abstract void setUrl(String url);
|
||||
|
||||
public abstract String getNotes();
|
||||
public abstract void setNotes(String notes);
|
||||
|
||||
public boolean isTan() {
|
||||
private boolean isTan() {
|
||||
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle() {
|
||||
if ( isTan() ) {
|
||||
return PMS_TAN_ENTRY + " " + getUsername();
|
||||
@@ -148,10 +128,9 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
|
||||
/**
|
||||
* Retrieve extra fields to show, key is the label, value is the value of field
|
||||
* @param pm Database
|
||||
* @return Map of label/value
|
||||
*/
|
||||
public Map<String, String> getExtraFields(PwDatabase pm) {
|
||||
public Map<String, String> getExtraFields() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -163,6 +142,14 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* If entry contains extra fields
|
||||
* @return true if there is extra fields
|
||||
*/
|
||||
public boolean containsExtraFields() {
|
||||
return !getExtraProtectedFields().keySet().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extra field to the list
|
||||
* @param label Label of field, must be unique
|
||||
@@ -183,13 +170,18 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backup of entry
|
||||
*/
|
||||
public void createBackup(PwDatabase db) {}
|
||||
|
||||
public EntrySearchStringIterator stringIterator() {
|
||||
return EntrySearchStringIterator.getInstance(this);
|
||||
}
|
||||
|
||||
public void touch(boolean modified, boolean touchParents) {
|
||||
Date now = new Date();
|
||||
|
||||
PwDate now = new PwDate();
|
||||
|
||||
setLastAccessTime(now);
|
||||
|
||||
if (modified) {
|
||||
@@ -222,69 +214,4 @@ public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
public int hashCode() {
|
||||
return getUUID() != null ? getUUID().hashCode() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Name
|
||||
*/
|
||||
public static class EntryNameComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryNameComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public EntryNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryTitleComp = object1.getTitle().compareToIgnoreCase(object2.getTitle());
|
||||
// If same title, can be different
|
||||
if (entryTitleComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryTitleComp = -entryTitleComp;
|
||||
|
||||
return entryTitleComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Creation
|
||||
*/
|
||||
public static class EntryCreationComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryCreationComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public EntryCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
|
||||
// If same creation, can be different
|
||||
if (entryCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryCreationComp = -entryCreationComp;
|
||||
|
||||
return entryCreationComp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,13 +42,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
package com.keepassdroid.database;
|
||||
|
||||
// PhoneID
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@@ -71,137 +65,204 @@ import java.util.UUID;
|
||||
* @author Naomaru Itoi <nao@phoneid.org>
|
||||
* @author Bill Zwicky <wrzwicky@pobox.com>
|
||||
* @author Dominik Reichl <dominik.reichl@t-online.de>
|
||||
* @author Jeremy Jamet <jeremy.jamet@kunzisoft.com>
|
||||
*/
|
||||
public class PwEntryV3 extends PwEntry {
|
||||
|
||||
public static final Date NEVER_EXPIRE = getNeverExpire();
|
||||
public static final Date NEVER_EXPIRE_BUG = getNeverExpireBug();
|
||||
public static final Date DEFAULT_DATE = getDefaultDate();
|
||||
public static final PwDate PW_NEVER_EXPIRE = new PwDate(NEVER_EXPIRE);
|
||||
public static final PwDate PW_NEVER_EXPIRE_BUG = new PwDate(NEVER_EXPIRE_BUG);
|
||||
public static final PwDate DEFAULT_PWDATE = new PwDate(DEFAULT_DATE);
|
||||
|
||||
|
||||
/** Size of byte buffer needed to hold this struct. */
|
||||
public static final String PMS_ID_BINDESC = "bin-stream";
|
||||
public static final String PMS_ID_TITLE = "Meta-Info";
|
||||
public static final String PMS_ID_USER = "SYSTEM";
|
||||
public static final String PMS_ID_URL = "$";
|
||||
private static final String PMS_ID_BINDESC = "bin-stream";
|
||||
private static final String PMS_ID_TITLE = "Meta-Info";
|
||||
private static final String PMS_ID_USER = "SYSTEM";
|
||||
private static final String PMS_ID_URL = "$";
|
||||
|
||||
// for tree traversing
|
||||
private PwGroupV3 parent = null;
|
||||
private int groupId;
|
||||
|
||||
|
||||
public int groupId;
|
||||
public String username;
|
||||
private byte[] password;
|
||||
private byte[] uuid;
|
||||
public String title;
|
||||
public String url;
|
||||
public String additional;
|
||||
|
||||
|
||||
public PwDate tCreation;
|
||||
public PwDate tLastMod;
|
||||
public PwDate tLastAccess;
|
||||
public PwDate tExpire;
|
||||
private String title;
|
||||
private String username;
|
||||
private byte[] password;
|
||||
private String url;
|
||||
private String additional;
|
||||
|
||||
/** A string describing what is in pBinaryData */
|
||||
public String binaryDesc;
|
||||
private String binaryDesc;
|
||||
private byte[] binaryData;
|
||||
|
||||
private static Date getDefaultDate() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2004);
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
cal.set(Calendar.HOUR, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
private static Date getNeverExpire() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2999);
|
||||
cal.set(Calendar.MONTH, 11);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 28);
|
||||
cal.set(Calendar.HOUR, 23);
|
||||
cal.set(Calendar.MINUTE, 59);
|
||||
cal.set(Calendar.SECOND, 59);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
/** This date was was accidentally being written
|
||||
* out when an entry was supposed to be marked as
|
||||
* expired. We'll use this to silently correct those
|
||||
* entries.
|
||||
* @return
|
||||
*/
|
||||
private static Date getNeverExpireBug() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.YEAR, 2999);
|
||||
cal.set(Calendar.MONTH, 11);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 30);
|
||||
cal.set(Calendar.HOUR, 23);
|
||||
cal.set(Calendar.MINUTE, 59);
|
||||
cal.set(Calendar.SECOND, 59);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static boolean IsNever(Date date) {
|
||||
return PwDate.IsSameDate(NEVER_EXPIRE, date);
|
||||
}
|
||||
|
||||
// for tree traversing
|
||||
public PwGroupV3 parent = null;
|
||||
|
||||
|
||||
public PwEntryV3() {
|
||||
super();
|
||||
}
|
||||
|
||||
/*
|
||||
public PwEntryV3(PwEntryV3 source) {
|
||||
assign(source);
|
||||
}
|
||||
*/
|
||||
|
||||
public PwEntryV3(PwGroupV3 p) {
|
||||
this(p, true, true);
|
||||
}
|
||||
|
||||
public PwEntryV3(PwGroupV3 p, boolean initId, boolean initDates) {
|
||||
|
||||
construct();
|
||||
parent = p;
|
||||
groupId = ((PwGroupIdV3)parent.getId()).getId();
|
||||
|
||||
if (initId) {
|
||||
Random random = new Random();
|
||||
uuid = new byte[16];
|
||||
random.nextBytes(uuid);
|
||||
}
|
||||
|
||||
if (initDates) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Date now = cal.getTime();
|
||||
tCreation = new PwDate(now);
|
||||
tLastAccess = new PwDate(now);
|
||||
tLastMod = new PwDate(now);
|
||||
tExpire = new PwDate(NEVER_EXPIRE);
|
||||
}
|
||||
groupId = ((PwGroupIdV3)parent.getId()).getId(); // TODO remove
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
if ( ! (source instanceof PwEntryV3) ) {
|
||||
throw new RuntimeException("DB version mix");
|
||||
}
|
||||
super.assign(source);
|
||||
PwEntryV3 src = (PwEntryV3) source;
|
||||
parent = src.parent;
|
||||
groupId = src.groupId;
|
||||
|
||||
title = src.title;
|
||||
username = src.username;
|
||||
int passLen = src.password.length;
|
||||
password = new byte[passLen];
|
||||
System.arraycopy(src.password, 0, password, 0, passLen);
|
||||
url = src.url;
|
||||
additional = src.additional;
|
||||
|
||||
binaryDesc = src.binaryDesc;
|
||||
if ( src.binaryData != null ) {
|
||||
int descLen = src.binaryData.length;
|
||||
binaryData = new byte[descLen];
|
||||
System.arraycopy(src.binaryData, 0, binaryData, 0, descLen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwEntryV3 clone() {
|
||||
PwEntryV3 newEntry = (PwEntryV3) super.clone();
|
||||
|
||||
// Attributes in parent
|
||||
addCloneAttributesToNewEntry(newEntry);
|
||||
|
||||
// Attributes here
|
||||
// newEntry.parent stay the same in copy
|
||||
// newEntry.groupId stay the same in copy
|
||||
// newEntry.title stay the same in copy
|
||||
// newEntry.username stay the same in copy
|
||||
if (password != null) {
|
||||
int passLen = password.length;
|
||||
password = new byte[passLen];
|
||||
System.arraycopy(password, 0, newEntry.password, 0, passLen);
|
||||
}
|
||||
// newEntry.url stay the same in copy
|
||||
// newEntry.additional stay the same in copy
|
||||
|
||||
// newEntry.binaryDesc stay the same in copy
|
||||
if ( binaryData != null ) {
|
||||
int descLen = binaryData.length;
|
||||
newEntry.binaryData = new byte[descLen];
|
||||
System.arraycopy(binaryData, 0, newEntry.binaryData, 0, descLen);
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroupV3 getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup parent) {
|
||||
this.parent = (PwGroupV3) parent;
|
||||
}
|
||||
|
||||
public int getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(int groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
return "";
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String user) {
|
||||
username = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotes() {
|
||||
return additional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotes(String notes) {
|
||||
additional = notes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void populateBlankFields(PwDatabaseV3 db) {
|
||||
// TODO verify and remove
|
||||
if (icon == null) {
|
||||
icon = db.iconFactory.getIcon(1);
|
||||
}
|
||||
|
||||
if (username == null) {
|
||||
username = "";
|
||||
}
|
||||
|
||||
if (password == null) {
|
||||
password = new byte[0];
|
||||
}
|
||||
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
url = "";
|
||||
}
|
||||
|
||||
if (additional == null) {
|
||||
additional = "";
|
||||
}
|
||||
|
||||
if (binaryDesc == null) {
|
||||
binaryDesc = "";
|
||||
}
|
||||
|
||||
if (binaryData == null) {
|
||||
binaryData = new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the actual password byte array.
|
||||
*/
|
||||
@Override
|
||||
public String getPassword(boolean decodeRef, PwDatabase db) {
|
||||
public String getPassword() {
|
||||
if (password == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return new String(password);
|
||||
}
|
||||
|
||||
@@ -209,7 +270,6 @@ public class PwEntryV3 extends PwEntry {
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fill byte array
|
||||
*/
|
||||
@@ -230,10 +290,8 @@ public class PwEntryV3 extends PwEntry {
|
||||
System.arraycopy( buf, offset, password, 0, len );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setPassword(String pass, PwDatabase db) {
|
||||
public void setPassword(String pass) {
|
||||
byte[] password;
|
||||
try {
|
||||
password = pass.getBytes("UTF-8");
|
||||
@@ -252,8 +310,6 @@ public class PwEntryV3 extends PwEntry {
|
||||
return binaryData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Securely erase old data before copying new. */
|
||||
public void setBinaryData( byte[] buf, int offset, int len ) {
|
||||
if( binaryData != null ) {
|
||||
@@ -264,6 +320,14 @@ public class PwEntryV3 extends PwEntry {
|
||||
System.arraycopy( buf, offset, binaryData, 0, len );
|
||||
}
|
||||
|
||||
public String getBinaryDesc() {
|
||||
return binaryDesc;
|
||||
}
|
||||
|
||||
public void setBinaryDesc(String binaryDesc) {
|
||||
this.binaryDesc = binaryDesc;
|
||||
}
|
||||
|
||||
// Determine if this is a MetaStream entry
|
||||
@Override
|
||||
public boolean isMetaStream() {
|
||||
@@ -280,249 +344,4 @@ public class PwEntryV3 extends PwEntry {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
|
||||
if ( ! (source instanceof PwEntryV3) ) {
|
||||
throw new RuntimeException("DB version mix");
|
||||
}
|
||||
|
||||
super.assign(source);
|
||||
|
||||
PwEntryV3 src = (PwEntryV3) source;
|
||||
assign(src);
|
||||
|
||||
}
|
||||
|
||||
private void assign(PwEntryV3 source) {
|
||||
title = source.title;
|
||||
url = source.url;
|
||||
groupId = source.groupId;
|
||||
username = source.username;
|
||||
additional = source.additional;
|
||||
uuid = source.uuid;
|
||||
|
||||
int passLen = source.password.length;
|
||||
password = new byte[passLen];
|
||||
System.arraycopy(source.password, 0, password, 0, passLen);
|
||||
|
||||
tCreation = (PwDate) source.tCreation.clone();
|
||||
tLastMod = (PwDate) source.tLastMod.clone();
|
||||
tLastAccess = (PwDate) source.tLastAccess.clone();
|
||||
tExpire = (PwDate) source.tExpire.clone();
|
||||
|
||||
binaryDesc = source.binaryDesc;
|
||||
|
||||
if ( source.binaryData != null ) {
|
||||
int descLen = source.binaryData.length;
|
||||
binaryData = new byte[descLen];
|
||||
System.arraycopy(source.binaryData, 0, binaryData, 0, descLen);
|
||||
}
|
||||
|
||||
parent = source.parent;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
PwEntryV3 newEntry = (PwEntryV3) super.clone();
|
||||
|
||||
if (password != null) {
|
||||
int passLen = password.length;
|
||||
password = new byte[passLen];
|
||||
System.arraycopy(password, 0, newEntry.password, 0, passLen);
|
||||
}
|
||||
|
||||
newEntry.tCreation = (PwDate) tCreation.clone();
|
||||
newEntry.tLastMod = (PwDate) tLastMod.clone();
|
||||
newEntry.tLastAccess = (PwDate) tLastAccess.clone();
|
||||
newEntry.tExpire = (PwDate) tExpire.clone();
|
||||
|
||||
newEntry.binaryDesc = binaryDesc;
|
||||
|
||||
if ( binaryData != null ) {
|
||||
int descLen = binaryData.length;
|
||||
newEntry.binaryData = new byte[descLen];
|
||||
System.arraycopy(binaryData, 0, newEntry.binaryData, 0, descLen);
|
||||
}
|
||||
|
||||
newEntry.parent = parent;
|
||||
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastAccessTime() {
|
||||
return tLastAccess.getJDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreationTime() {
|
||||
return tCreation.getJDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getExpiryTime() {
|
||||
return tExpire.getJDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastModificationTime() {
|
||||
return tLastMod.getJDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Date create) {
|
||||
tCreation = new PwDate(create);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModificationTime(Date mod) {
|
||||
tLastMod = new PwDate(mod);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessTime(Date access) {
|
||||
tLastAccess = new PwDate(access);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpires(boolean expires) {
|
||||
if (!expires) {
|
||||
tExpire = PW_NEVER_EXPIRE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpiryTime(Date expires) {
|
||||
tExpire = new PwDate(expires);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroupV3 getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return Types.bytestoUUID(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUUID(UUID u) {
|
||||
uuid = Types.UUIDtoBytes(u);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername(boolean decodeRef, PwDatabase db) {
|
||||
if (username == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String user, PwDatabase db) {
|
||||
username = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle(boolean decodeRef, PwDatabase db) {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(String title, PwDatabase db) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotes(boolean decodeRef, PwDatabase db) {
|
||||
return additional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotes(String notes, PwDatabase db) {
|
||||
additional = notes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(boolean decodeRef, PwDatabase db) {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrl(String url, PwDatabase db) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expires() {
|
||||
return ! IsNever(tExpire.getJDate());
|
||||
}
|
||||
|
||||
public void populateBlankFields(PwDatabaseV3 db) {
|
||||
if (icon == null) {
|
||||
icon = db.iconFactory.getIcon(1);
|
||||
}
|
||||
|
||||
if (username == null) {
|
||||
username = "";
|
||||
}
|
||||
|
||||
if (password == null) {
|
||||
password = new byte[0];
|
||||
}
|
||||
|
||||
if (uuid == null) {
|
||||
uuid = Types.UUIDtoBytes(UUID.randomUUID());
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
url = "";
|
||||
}
|
||||
|
||||
if (additional == null) {
|
||||
additional = "";
|
||||
}
|
||||
|
||||
if (tCreation == null) {
|
||||
tCreation = DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tLastMod == null) {
|
||||
tLastMod = DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tLastAccess == null) {
|
||||
tLastAccess = DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tExpire == null) {
|
||||
tExpire = PW_NEVER_EXPIRE;
|
||||
}
|
||||
|
||||
if (binaryDesc == null) {
|
||||
binaryDesc = "";
|
||||
}
|
||||
|
||||
if (binaryData == null) {
|
||||
binaryData = new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup parent) {
|
||||
this.parent = (PwGroupV3) parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,293 +21,205 @@ package com.keepassdroid.database;
|
||||
|
||||
import com.keepassdroid.database.security.ProtectedBinary;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
import com.keepassdroid.utils.SprEngineV4;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
|
||||
public static final String STR_TITLE = "Title";
|
||||
public static final String STR_USERNAME = "UserName";
|
||||
public static final String STR_PASSWORD = "Password";
|
||||
public static final String STR_URL = "URL";
|
||||
public static final String STR_NOTES = "Notes";
|
||||
|
||||
public PwGroupV4 parent;
|
||||
public UUID uuid = PwDatabaseV4.UUID_ZERO;
|
||||
private HashMap<String, ProtectedString> fields = new HashMap<>();
|
||||
public HashMap<String, ProtectedBinary> binaries = new HashMap<>();
|
||||
public PwIconCustom customIcon = PwIconCustom.ZERO;
|
||||
public String foregroundColor = "";
|
||||
public String backgroupColor = "";
|
||||
public String overrideURL = "";
|
||||
public AutoType autoType = new AutoType();
|
||||
public ArrayList<PwEntryV4> history = new ArrayList<>();
|
||||
|
||||
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date creation = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date lastMod = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date lastAccess = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date expireDate = PwDatabaseV4.DEFAULT_NOW;
|
||||
private boolean expires = false;
|
||||
private long usageCount = 0;
|
||||
public String url = "";
|
||||
public String additional = "";
|
||||
public String tags = "";
|
||||
public Map<String, String> customData = new HashMap<String, String>();
|
||||
|
||||
public class AutoType implements Cloneable, Serializable {
|
||||
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<String, String>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object clone() {
|
||||
AutoType auto;
|
||||
try {
|
||||
auto = (AutoType) super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
assert(false);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
auto.windowSeqPairs = (HashMap<String, String>) windowSeqPairs.clone();
|
||||
|
||||
return auto;
|
||||
|
||||
}
|
||||
|
||||
public void put(String key, String value) {
|
||||
windowSeqPairs.put(key, value);
|
||||
}
|
||||
|
||||
public Set<Entry<String, String>> entrySet() {
|
||||
return windowSeqPairs.entrySet();
|
||||
}
|
||||
|
||||
}
|
||||
// To decode each field not serializable
|
||||
private transient PwDatabase mDatabase = null;
|
||||
private transient boolean mDecodeRef = false;
|
||||
|
||||
private PwGroupV4 parent;
|
||||
private PwIconCustom customIcon = PwIconCustom.ZERO;
|
||||
private long usageCount = 0;
|
||||
private PwDate parentGroupLastMod = new PwDate();
|
||||
private Map<String, String> customData = new HashMap<>();
|
||||
|
||||
private HashMap<String, ProtectedString> fields = new HashMap<>();
|
||||
private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
|
||||
private String foregroundColor = "";
|
||||
private String backgroupColor = "";
|
||||
private String overrideURL = "";
|
||||
private AutoType autoType = new AutoType();
|
||||
private ArrayList<PwEntryV4> history = new ArrayList<>();
|
||||
|
||||
private String url = "";
|
||||
private String additional = "";
|
||||
private String tags = "";
|
||||
|
||||
public PwEntryV4() {
|
||||
|
||||
super();
|
||||
}
|
||||
|
||||
public PwEntryV4(PwGroupV4 p) {
|
||||
this(p, true, true);
|
||||
}
|
||||
|
||||
public PwEntryV4(PwGroupV4 p, boolean initId, boolean initDates) {
|
||||
construct();
|
||||
parent = p;
|
||||
|
||||
if (initId) {
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
if (initDates) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Date now = cal.getTime();
|
||||
creation = now;
|
||||
lastAccess = now;
|
||||
lastMod = now;
|
||||
expires = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
if ( ! (source instanceof PwEntryV4) ) {
|
||||
throw new RuntimeException("DB version mix.");
|
||||
}
|
||||
super.assign(source);
|
||||
PwEntryV4 src = (PwEntryV4) source;
|
||||
parent = src.parent;
|
||||
customIcon = src.customIcon;
|
||||
usageCount = src.usageCount;
|
||||
parentGroupLastMod = src.parentGroupLastMod;
|
||||
// TODO customData
|
||||
|
||||
fields = src.fields;
|
||||
binaries = src.binaries;
|
||||
foregroundColor = src.foregroundColor;
|
||||
backgroupColor = src.backgroupColor;
|
||||
overrideURL = src.overrideURL;
|
||||
autoType = src.autoType;
|
||||
history = src.history;
|
||||
|
||||
url = src.url;
|
||||
additional = src.additional;
|
||||
tags = src.tags;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PwEntryV4 clone() {
|
||||
PwEntryV4 newEntry = (PwEntryV4) super.clone();
|
||||
|
||||
// Attributes in parent
|
||||
addCloneAttributesToNewEntry(newEntry);
|
||||
|
||||
// Attributes here
|
||||
// newEntry.parent stay the same in copy
|
||||
newEntry.customIcon = new PwIconCustom(this.customIcon);
|
||||
// newEntry.usageCount stay the same in copy
|
||||
newEntry.parentGroupLastMod = this.parentGroupLastMod.clone();
|
||||
// TODO customData make copy from hashmap
|
||||
|
||||
newEntry.fields = (HashMap<String, ProtectedString>) this.fields.clone();
|
||||
newEntry.binaries = (HashMap<String, ProtectedBinary>) this.binaries.clone();
|
||||
// newEntry.foregroundColor stay the same in copy
|
||||
// newEntry.backgroupColor stay the same in copy
|
||||
// newEntry.overrideURL stay the same in copy
|
||||
newEntry.autoType = autoType.clone();
|
||||
newEntry.history = (ArrayList<PwEntryV4>) this.history.clone();
|
||||
|
||||
// newEntry.url stay the same in copy
|
||||
// newEntry.additional stay the same in copy
|
||||
// newEntry.tags stay the same in copy
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PwEntry clone(boolean deepStrings) {
|
||||
PwEntryV4 entry = (PwEntryV4) super.clone(deepStrings);
|
||||
|
||||
if (deepStrings) {
|
||||
entry.fields = (HashMap<String, ProtectedString>) fields.clone();
|
||||
}
|
||||
|
||||
return entry;
|
||||
public void startToDecodeReference(PwDatabase db) {
|
||||
this.mDatabase = db;
|
||||
this.mDecodeRef = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
|
||||
if ( ! (source instanceof PwEntryV4) ) {
|
||||
throw new RuntimeException("DB version mix.");
|
||||
}
|
||||
|
||||
super.assign(source);
|
||||
|
||||
PwEntryV4 src = (PwEntryV4) source;
|
||||
assign(src);
|
||||
}
|
||||
|
||||
private void assign(PwEntryV4 source) {
|
||||
parent = source.parent;
|
||||
uuid = source.uuid;
|
||||
fields = source.fields;
|
||||
binaries = source.binaries;
|
||||
customIcon = source.customIcon;
|
||||
foregroundColor = source.foregroundColor;
|
||||
backgroupColor = source.backgroupColor;
|
||||
overrideURL = source.overrideURL;
|
||||
autoType = source.autoType;
|
||||
history = source.history;
|
||||
parentGroupLastMod = source.parentGroupLastMod;
|
||||
creation = source.creation;
|
||||
lastMod = source.lastMod;
|
||||
lastAccess = source.lastAccess;
|
||||
expireDate = source.expireDate;
|
||||
expires = source.expires;
|
||||
usageCount = source.usageCount;
|
||||
url = source.url;
|
||||
additional = source.additional;
|
||||
|
||||
public void endToDecodeReference(PwDatabase db) {
|
||||
this.mDatabase = null;
|
||||
this.mDecodeRef = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
PwEntryV4 newEntry = (PwEntryV4) super.clone();
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
private String decodeRefKey(boolean decodeRef, String key, PwDatabase db) {
|
||||
private String decodeRefKey(boolean decodeRef, String key) {
|
||||
String text = getString(key);
|
||||
if (decodeRef) {
|
||||
text = decodeRef(text, db);
|
||||
text = decodeRef(text, mDatabase);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private String decodeRef(String text, PwDatabase db) {
|
||||
if (db == null) { return text; }
|
||||
|
||||
SprEngine spr = SprEngine.getInstance(db);
|
||||
SprEngineV4 spr = new SprEngineV4();
|
||||
return spr.compile(text, this, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername(boolean decodeRef, PwDatabase db) {
|
||||
return decodeRefKey(decodeRef, STR_USERNAME, db);
|
||||
public String getUsername() {
|
||||
return decodeRefKey(mDecodeRef, STR_USERNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle(boolean decodeRef, PwDatabase db) {
|
||||
return decodeRefKey(decodeRef, STR_TITLE, db);
|
||||
public String getTitle() {
|
||||
return decodeRefKey(mDecodeRef, STR_TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword(boolean decodeRef, PwDatabase db) {
|
||||
return decodeRefKey(decodeRef, STR_PASSWORD, db);
|
||||
public String getPassword() {
|
||||
return decodeRefKey(mDecodeRef, STR_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastAccessTime() {
|
||||
return lastAccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreationTime() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getExpiryTime() {
|
||||
return expireDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastModificationTime() {
|
||||
return lastMod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(String title, PwDatabase d) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) d;
|
||||
public void setTitle(String title) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
|
||||
boolean protect = db.memoryProtection.protectTitle;
|
||||
|
||||
setString(STR_TITLE, title, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String user, PwDatabase d) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) d;
|
||||
public void setUsername(String user) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
|
||||
boolean protect = db.memoryProtection.protectUserName;
|
||||
|
||||
setString(STR_USERNAME, user, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String pass, PwDatabase d) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) d;
|
||||
public void setPassword(String pass) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
|
||||
boolean protect = db.memoryProtection.protectPassword;
|
||||
|
||||
setString(STR_PASSWORD, pass, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrl(String url, PwDatabase d) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) d;
|
||||
public void setUrl(String url) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
|
||||
boolean protect = db.memoryProtection.protectUrl;
|
||||
|
||||
setString(STR_URL, url, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotes(String notes, PwDatabase d) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) d;
|
||||
public void setNotes(String notes) {
|
||||
PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
|
||||
boolean protect = db.memoryProtection.protectNotes;
|
||||
|
||||
setString(STR_NOTES, notes, protect);
|
||||
}
|
||||
|
||||
public void setCreationTime(Date date) {
|
||||
creation = date;
|
||||
}
|
||||
|
||||
public void setExpiryTime(Date date) {
|
||||
expireDate = date;
|
||||
}
|
||||
|
||||
public void setLastAccessTime(Date date) {
|
||||
lastAccess = date;
|
||||
}
|
||||
|
||||
public void setLastModificationTime(Date date) {
|
||||
lastMod = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroupV4 getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
@Override
|
||||
public void setParent(PwGroup parent) {
|
||||
this.parent = (PwGroupV4) parent;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setUUID(UUID u) {
|
||||
uuid = u;
|
||||
}
|
||||
|
||||
public String getString(String key) {
|
||||
ProtectedString value = fields.get(key);
|
||||
|
||||
if ( value == null ) return new String("");
|
||||
if ( value == null ) return "";
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
@@ -317,7 +229,15 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
fields.put(key, ps);
|
||||
}
|
||||
|
||||
public Date getLocationChanged() {
|
||||
public PwIconCustom getCustomIcon() {
|
||||
return customIcon;
|
||||
}
|
||||
|
||||
public void setCustomIcon(PwIconCustom icon) {
|
||||
this.customIcon = icon;
|
||||
}
|
||||
|
||||
public PwDate getLocationChanged() {
|
||||
return parentGroupLastMod;
|
||||
}
|
||||
|
||||
@@ -325,109 +245,32 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
return usageCount;
|
||||
}
|
||||
|
||||
public void setLocationChanged(Date date) {
|
||||
public void setLocationChanged(PwDate date) {
|
||||
parentGroupLastMod = date;
|
||||
}
|
||||
|
||||
public void setUsageCount(long count) {
|
||||
usageCount = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
public void setExpires(boolean exp) {
|
||||
expires = exp;
|
||||
@Override
|
||||
public String getNotes() {
|
||||
return decodeRefKey(mDecodeRef, STR_NOTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotes(boolean decodeRef, PwDatabase db) {
|
||||
return decodeRefKey(decodeRef, STR_NOTES, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(boolean decodeRef, PwDatabase db) {
|
||||
return decodeRefKey(decodeRef, STR_URL, db);
|
||||
public String getUrl() {
|
||||
return decodeRefKey(mDecodeRef, STR_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwIcon getIcon() {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
||||
return super.getIcon();
|
||||
} else {
|
||||
return customIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public void createBackup(PwDatabaseV4 db) {
|
||||
PwEntryV4 copy = cloneDeep();
|
||||
copy.history = new ArrayList<>();
|
||||
history.add(copy);
|
||||
|
||||
if (db != null) maintainBackups(db);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PwEntryV4 cloneDeep() {
|
||||
PwEntryV4 entry = (PwEntryV4) clone(true);
|
||||
|
||||
entry.binaries = (HashMap<String, ProtectedBinary>) binaries.clone();
|
||||
entry.history = (ArrayList<PwEntryV4>) history.clone();
|
||||
entry.autoType = (AutoType) autoType.clone();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private boolean maintainBackups(PwDatabaseV4 db) {
|
||||
boolean deleted = false;
|
||||
|
||||
int maxItems = db.historyMaxItems;
|
||||
if (maxItems >= 0) {
|
||||
while (history.size() > maxItems) {
|
||||
removeOldestBackup();
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
long maxSize = db.historyMaxSize;
|
||||
if (maxSize >= 0) {
|
||||
while(true) {
|
||||
long histSize = 0;
|
||||
for (PwEntryV4 entry : history) {
|
||||
histSize += entry.getSize();
|
||||
}
|
||||
|
||||
if (histSize > maxSize) {
|
||||
removeOldestBackup();
|
||||
deleted = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
private void removeOldestBackup() {
|
||||
Date min = null;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < history.size(); i++) {
|
||||
PwEntry entry = history.get(i);
|
||||
Date lastMod = entry.getLastModificationTime();
|
||||
if ((min == null) || lastMod.before(min)) {
|
||||
index = i;
|
||||
min = lastMod;
|
||||
}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
history.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowExtraFields() {
|
||||
@@ -444,7 +287,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
if (fields.size() > 0) {
|
||||
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
if (!PwEntryV4.IsStandardField(key)) {
|
||||
if (!PwEntryV4.isStandardField(key)) {
|
||||
protectedFields.put(key, pair.getValue());
|
||||
}
|
||||
}
|
||||
@@ -453,23 +296,23 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraFields(PwDatabase pm) {
|
||||
Map<String, String> extraFields = super.getExtraFields(pm);
|
||||
SprEngine spr = SprEngine.getInstance(pm);
|
||||
public Map<String, String> getExtraFields() {
|
||||
Map<String, String> extraFields = super.getExtraFields();
|
||||
SprEngineV4 spr = new SprEngineV4();
|
||||
// Display custom fields
|
||||
if (fields.size() > 0) {
|
||||
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
// TODO Add hidden style for protection field
|
||||
if (!PwEntryV4.IsStandardField(key)) {
|
||||
extraFields.put(key, spr.compile(pair.getValue().toString(), this, pm));
|
||||
if (!PwEntryV4.isStandardField(key)) {
|
||||
extraFields.put(key, spr.compile(pair.getValue().toString(), this, mDatabase));
|
||||
}
|
||||
}
|
||||
}
|
||||
return extraFields;
|
||||
}
|
||||
|
||||
public static boolean IsStandardField(String key) {
|
||||
private static boolean isStandardField(String key) {
|
||||
return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|
||||
|| key.equals(STR_PASSWORD) || key.equals(STR_URL)
|
||||
|| key.equals(STR_NOTES);
|
||||
@@ -484,13 +327,93 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, ProtectedString> pair = iter.next();
|
||||
if (!PwEntryV4.IsStandardField(pair.getKey())) {
|
||||
if (!PwEntryV4.isStandardField(pair.getKey())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
|
||||
|
||||
public HashMap<String, ProtectedBinary> getBinaries() {
|
||||
return binaries;
|
||||
}
|
||||
|
||||
public void putProtectedBinary(String key, ProtectedBinary value) {
|
||||
binaries.put(key, value);
|
||||
}
|
||||
|
||||
public String getForegroundColor() {
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
public void setForegroundColor(String color) {
|
||||
this.foregroundColor = color;
|
||||
}
|
||||
|
||||
public String getBackgroupColor() {
|
||||
return backgroupColor;
|
||||
}
|
||||
|
||||
public void setBackgroupColor(String color) {
|
||||
this.backgroupColor = color;
|
||||
}
|
||||
|
||||
public String getOverrideURL() {
|
||||
return overrideURL;
|
||||
}
|
||||
|
||||
public void setOverrideURL(String overrideURL) {
|
||||
this.overrideURL = overrideURL;
|
||||
}
|
||||
|
||||
public AutoType getAutoType() {
|
||||
return autoType;
|
||||
}
|
||||
|
||||
public void setAutoType(AutoType autoType) {
|
||||
this.autoType = autoType;
|
||||
}
|
||||
|
||||
public ArrayList<PwEntryV4> getHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
public void setHistory(ArrayList<PwEntryV4> history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
public void addToHistory(PwEntryV4 entry) {
|
||||
history.add(entry);
|
||||
}
|
||||
|
||||
public int sizeOfHistory() {
|
||||
return history.size();
|
||||
}
|
||||
|
||||
public String getAdditional() {
|
||||
return additional;
|
||||
}
|
||||
|
||||
public void setAdditional(String additional) {
|
||||
this.additional = additional;
|
||||
}
|
||||
|
||||
public String getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(String tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public void putCustomData(String key, String value) {
|
||||
customData.put(key, value);
|
||||
}
|
||||
|
||||
public boolean containsCustomData() {
|
||||
return customData.size() > 0;
|
||||
}
|
||||
|
||||
private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
|
||||
public long getSize() {
|
||||
long size = FIXED_LENGTH_SIZE;
|
||||
|
||||
@@ -520,6 +443,68 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBackup(PwDatabase db) {
|
||||
super.createBackup(db);
|
||||
|
||||
PwEntryV4 copy = clone();
|
||||
copy.history = new ArrayList<>();
|
||||
history.add(copy);
|
||||
|
||||
if (db != null)
|
||||
if (db instanceof PwDatabaseV4)
|
||||
maintainBackups((PwDatabaseV4) db);
|
||||
}
|
||||
|
||||
private boolean maintainBackups(PwDatabaseV4 db) {
|
||||
boolean deleted = false;
|
||||
|
||||
int maxItems = db.historyMaxItems;
|
||||
if (maxItems >= 0) {
|
||||
while (history.size() > maxItems) {
|
||||
removeOldestBackup();
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
long maxSize = db.historyMaxSize;
|
||||
if (maxSize >= 0) {
|
||||
while(true) {
|
||||
long histSize = 0;
|
||||
for (PwEntryV4 entry : history) {
|
||||
histSize += entry.getSize();
|
||||
}
|
||||
|
||||
if (histSize > maxSize) {
|
||||
removeOldestBackup();
|
||||
deleted = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
private void removeOldestBackup() {
|
||||
Date min = null;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < history.size(); i++) {
|
||||
PwEntry entry = history.get(i);
|
||||
Date lastMod = entry.getLastModificationTime().getDate();
|
||||
if ((min == null) || lastMod.before(min)) {
|
||||
index = i;
|
||||
min = lastMod;
|
||||
}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
history.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touch(boolean modified, boolean touchParents) {
|
||||
super.touch(modified, touchParents);
|
||||
@@ -529,19 +514,13 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
|
||||
@Override
|
||||
public void touchLocation() {
|
||||
parentGroupLastMod = new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup parent) {
|
||||
this.parent = (PwGroupV4) parent;
|
||||
parentGroupLastMod = new PwDate();
|
||||
}
|
||||
|
||||
public boolean isSearchingEnabled() {
|
||||
if (parent != null) {
|
||||
return parent.isSearchEnabled();
|
||||
}
|
||||
|
||||
return PwGroupV4.DEFAULT_SEARCHING_ENABLED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,24 +24,36 @@ import com.keepassdroid.utils.StrUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PwGroup extends PwNode {
|
||||
|
||||
// TODO Change dependency and make private
|
||||
public List<PwGroup> childGroups = new ArrayList<>();
|
||||
public List<PwEntry> childEntries = new ArrayList<>();
|
||||
public String name = "";
|
||||
public PwIconStandard icon;
|
||||
protected String name = "";
|
||||
|
||||
private List<PwNode> children = new ArrayList<>();
|
||||
protected List<PwGroup> childGroups = new ArrayList<>();
|
||||
protected List<PwEntry> childEntries = new ArrayList<>();
|
||||
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
setId(newId);
|
||||
name = nm;
|
||||
}
|
||||
|
||||
public List<PwGroup> getChildGroups() {
|
||||
return childGroups;
|
||||
}
|
||||
|
||||
public List<PwEntry> getChildEntries() {
|
||||
return childEntries;
|
||||
}
|
||||
|
||||
public void setGroups(List<PwGroup> groups) {
|
||||
childGroups = groups;
|
||||
}
|
||||
|
||||
public void setEntries(List<PwEntry> entries) {
|
||||
childEntries = entries;
|
||||
}
|
||||
|
||||
public void addChildGroup(PwGroup group) {
|
||||
this.childGroups.add(group);
|
||||
}
|
||||
@@ -50,6 +62,15 @@ public abstract class PwGroup extends PwNode {
|
||||
this.childEntries.add(entry);
|
||||
}
|
||||
|
||||
// Todo parameter type
|
||||
public PwGroup getChildGroupAt(int number) {
|
||||
return this.childGroups.get(number);
|
||||
}
|
||||
|
||||
public PwEntry getChildEntryAt(int number) {
|
||||
return this.childEntries.get(number);
|
||||
}
|
||||
|
||||
public void removeChildGroup(PwGroup group) {
|
||||
this.childGroups.remove(group);
|
||||
}
|
||||
@@ -76,7 +97,7 @@ public abstract class PwGroup extends PwNode {
|
||||
* @return List of direct children (one level below) as PwNode
|
||||
*/
|
||||
public List<PwNode> getDirectChildren() {
|
||||
children.clear();
|
||||
List<PwNode> children = new ArrayList<>();
|
||||
children.addAll(childGroups);
|
||||
for(PwEntry child : childEntries) {
|
||||
if (!child.isMetaStream())
|
||||
@@ -85,14 +106,6 @@ public abstract class PwGroup extends PwNode {
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of direct elements in Node (one level below)
|
||||
* @return Size of child elements, default is 0
|
||||
*/
|
||||
public int numberOfDirectChildren() {
|
||||
return childGroups.size() + childEntries.size();
|
||||
}
|
||||
|
||||
public boolean isContainedIn(PwGroup container) {
|
||||
PwGroup cur = this;
|
||||
while (cur != null) {
|
||||
@@ -112,24 +125,20 @@ public abstract class PwGroup extends PwNode {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract Date getLastMod();
|
||||
|
||||
public PwIcon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public abstract void setLastAccessTime(Date date);
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public abstract void setLastModificationTime(Date date);
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean allowAddEntryIfIsRoot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void touch(boolean modified, boolean touchParents) {
|
||||
Date now = new Date();
|
||||
PwDate now = new PwDate();
|
||||
setLastAccessTime(now);
|
||||
|
||||
if (modified) {
|
||||
@@ -247,68 +256,4 @@ public abstract class PwGroup extends PwNode {
|
||||
PwGroupId groupId = getId();
|
||||
return groupId != null ? groupId.hashCode() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupNameComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupNameComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public GroupNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupNameComp = object1.getName().compareToIgnoreCase(object2.getName());
|
||||
// If same name, can be different
|
||||
if (groupNameComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupNameComp = -groupNameComp;
|
||||
|
||||
return groupNameComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupCreationComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupCreationComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public GroupCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
|
||||
// If same creation, can be different
|
||||
if (groupCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupCreationComp = -groupCreationComp;
|
||||
|
||||
return groupCreationComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ public class PwGroupIdV3 extends PwGroupId {
|
||||
if ( ! (compare instanceof PwGroupIdV3) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PwGroupIdV3 cmp = (PwGroupIdV3) compare;
|
||||
return id == cmp.id;
|
||||
}
|
||||
@@ -46,6 +45,4 @@ public class PwGroupIdV3 extends PwGroupId {
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ public class PwGroupIdV4 extends PwGroupId {
|
||||
if ( ! (id instanceof PwGroupIdV4) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PwGroupIdV4 v4 = (PwGroupIdV4) id;
|
||||
|
||||
return uuid.equals(v4.uuid);
|
||||
}
|
||||
|
||||
@@ -28,5 +26,4 @@ public class PwGroupIdV4 extends PwGroupId {
|
||||
public UUID getId() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file was derived from
|
||||
|
||||
Copyright 2007 Naomaru Itoi <nao@phoneid.org>
|
||||
|
||||
This file was derived from
|
||||
|
||||
Java clone of KeePass - A KeePass file viewer for Java
|
||||
Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
|
||||
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
@@ -30,15 +20,6 @@ Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
|
||||
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Brian Pellin <bpellin@gmail.com>
|
||||
* @author Naomaru Itoi <nao@phoneid.org>
|
||||
@@ -47,39 +28,46 @@ import java.util.List;
|
||||
*/
|
||||
public class PwGroupV3 extends PwGroup {
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static final Date NEVER_EXPIRE = PwEntryV3.NEVER_EXPIRE;
|
||||
|
||||
/** Size of byte buffer needed to hold this struct. */
|
||||
public static final int BUF_SIZE = 124;
|
||||
|
||||
// for tree traversing
|
||||
public PwGroupV3 parent = null;
|
||||
private PwGroupV3 parent = null;
|
||||
private int groupId;
|
||||
|
||||
public int groupId;
|
||||
|
||||
public PwDate tCreation;
|
||||
public PwDate tLastMod;
|
||||
public PwDate tLastAccess;
|
||||
public PwDate tExpire;
|
||||
|
||||
public int level; // short
|
||||
private int level = 0; // short
|
||||
|
||||
/** Used by KeePass internally, don't use */
|
||||
public int flags;
|
||||
|
||||
public void setGroups(List<PwGroup> groups) {
|
||||
childGroups = groups;
|
||||
}
|
||||
|
||||
private int flags;
|
||||
|
||||
public PwGroupV3() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroup getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup prt) {
|
||||
parent = (PwGroupV3) prt;
|
||||
level = parent.getLevel() + 1;
|
||||
}
|
||||
|
||||
public int getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(int groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroupId getId() {
|
||||
return new PwGroupIdV3(groupId);
|
||||
@@ -91,76 +79,27 @@ public class PwGroupV3 extends PwGroup {
|
||||
groupId = id3.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(int flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastMod() {
|
||||
return tLastMod.getJDate();
|
||||
}
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup prt) {
|
||||
parent = (PwGroupV3) prt;
|
||||
level = parent.level + 1;
|
||||
|
||||
}
|
||||
public void populateBlankFields(PwDatabaseV3 db) {
|
||||
// TODO populate blanck field
|
||||
if (icon == null) {
|
||||
icon = db.iconFactory.getIcon(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
super.initNewGroup(nm, newId);
|
||||
|
||||
Date now = Calendar.getInstance().getTime();
|
||||
tCreation = new PwDate(now);
|
||||
tLastAccess = new PwDate(now);
|
||||
tLastMod = new PwDate(now);
|
||||
tExpire = new PwDate(PwGroupV3.NEVER_EXPIRE);
|
||||
|
||||
}
|
||||
|
||||
public void populateBlankFields(PwDatabaseV3 db) {
|
||||
if (icon == null) {
|
||||
icon = db.iconFactory.getIcon(1);
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
name = "";
|
||||
}
|
||||
|
||||
if (tCreation == null) {
|
||||
tCreation = PwEntryV3.DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tLastMod == null) {
|
||||
tLastMod = PwEntryV3.DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tLastAccess == null) {
|
||||
tLastAccess = PwEntryV3.DEFAULT_PWDATE;
|
||||
}
|
||||
|
||||
if (tExpire == null) {
|
||||
tExpire = PwEntryV3.DEFAULT_PWDATE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessTime(Date date) {
|
||||
tLastAccess = new PwDate(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModificationTime(Date date) {
|
||||
tLastMod = new PwDate(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreationTime() {
|
||||
if(tCreation != null)
|
||||
return tCreation.getJDate();
|
||||
else
|
||||
return new Date();
|
||||
}
|
||||
if (name == null) {
|
||||
name = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,46 +19,37 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
|
||||
//public static final int FOLDER_ICON = 48;
|
||||
public static final boolean DEFAULT_SEARCHING_ENABLED = true;
|
||||
|
||||
public PwGroupV4 parent = null;
|
||||
public UUID uuid = PwDatabaseV4.UUID_ZERO;
|
||||
public String notes = "";
|
||||
public PwIconCustom customIcon = PwIconCustom.ZERO;
|
||||
public boolean isExpanded = true;
|
||||
public String defaultAutoTypeSequence = "";
|
||||
public Boolean enableAutoType = null;
|
||||
public Boolean enableSearching = null;
|
||||
public UUID lastTopVisibleEntry = PwDatabaseV4.UUID_ZERO;
|
||||
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date creation = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date lastMod = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date lastAccess = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date expireDate = PwDatabaseV4.DEFAULT_NOW;
|
||||
private boolean expires = false;
|
||||
private long usageCount = 0;
|
||||
public Map<String, String> customData = new HashMap<String, String>();
|
||||
|
||||
public PwGroupV4() {}
|
||||
private PwGroupV4 parent = null;
|
||||
private UUID uuid = PwDatabase.UUID_ZERO;
|
||||
private PwIconCustom customIcon = PwIconCustom.ZERO;
|
||||
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 = "";
|
||||
private Boolean enableAutoType = null;
|
||||
private Boolean enableSearching = null;
|
||||
private UUID lastTopVisibleEntry = PwDatabase.UUID_ZERO;
|
||||
|
||||
public PwGroupV4() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PwGroupV4(boolean createUUID, boolean setTimes, String name, PwIconStandard icon) {
|
||||
if (createUUID) {
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
if (setTimes) {
|
||||
creation = lastMod = lastAccess = new Date();
|
||||
}
|
||||
|
||||
public PwGroupV4(String name, PwIconStandard icon) {
|
||||
super.construct();
|
||||
this.uuid = UUID.randomUUID();
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
}
|
||||
@@ -66,65 +57,46 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
@Override
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
super.initNewGroup(nm, newId);
|
||||
|
||||
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
|
||||
parentGroupLastMod = new PwDate();
|
||||
}
|
||||
|
||||
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership) {
|
||||
AddGroup(subGroup, takeOwnership, false);
|
||||
}
|
||||
|
||||
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership, boolean updateLocationChanged) {
|
||||
public void AddGroup(PwGroupV4 subGroup) {
|
||||
if ( subGroup == null ) throw new RuntimeException("subGroup");
|
||||
|
||||
childGroups.add(subGroup);
|
||||
|
||||
if ( takeOwnership ) subGroup.parent = this;
|
||||
|
||||
if ( updateLocationChanged ) subGroup.parentGroupLastMod = new Date(System.currentTimeMillis());
|
||||
|
||||
}
|
||||
subGroup.parent = this;
|
||||
}
|
||||
|
||||
public void AddEntry(PwEntryV4 pe, boolean takeOwnership) {
|
||||
AddEntry(pe, takeOwnership, false);
|
||||
}
|
||||
|
||||
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
|
||||
public void AddEntry(PwEntryV4 pe) {
|
||||
assert(pe != null);
|
||||
|
||||
addChildEntry(pe);
|
||||
|
||||
if ( takeOwnership ) pe.parent = this;
|
||||
|
||||
if ( updateLocationChanged ) pe.setLocationChanged(new Date(System.currentTimeMillis()));
|
||||
}
|
||||
pe.setParent(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroup getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void buildChildGroupsRecursive(List<PwGroup> list) {
|
||||
list.add(this);
|
||||
|
||||
for ( int i = 0; i < numbersOfChildGroups(); i++) {
|
||||
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
|
||||
child.buildChildGroupsRecursive(list);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void buildChildEntriesRecursive(List<PwEntry> list) {
|
||||
for ( int i = 0; i < numbersOfChildEntries(); i++ ) {
|
||||
list.add(childEntries.get(i));
|
||||
}
|
||||
|
||||
for ( int i = 0; i < numbersOfChildGroups(); i++ ) {
|
||||
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
|
||||
child.buildChildEntriesRecursive(list);
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void setParent(PwGroup prt) {
|
||||
parent = (PwGroupV4) prt;
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUUID(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public PwIconCustom getCustomIcon() {
|
||||
return customIcon;
|
||||
}
|
||||
|
||||
public void setCustomIcon(PwIconCustom icon) {
|
||||
this.customIcon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwGroupId getId() {
|
||||
@@ -138,78 +110,35 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastMod() {
|
||||
public PwDate getLocationChanged() {
|
||||
return parentGroupLastMod;
|
||||
}
|
||||
|
||||
public Date getCreationTime() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
return expireDate;
|
||||
}
|
||||
|
||||
public Date getLastAccessTime() {
|
||||
return lastAccess;
|
||||
}
|
||||
|
||||
public Date getLastModificationTime() {
|
||||
return lastMod;
|
||||
}
|
||||
|
||||
public Date getLocationChanged() {
|
||||
return parentGroupLastMod;
|
||||
}
|
||||
|
||||
public long getUsageCount() {
|
||||
return usageCount;
|
||||
}
|
||||
|
||||
public void setCreationTime(Date date) {
|
||||
creation = date;
|
||||
}
|
||||
|
||||
public void setExpiryTime(Date date) {
|
||||
expireDate = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessTime(Date date) {
|
||||
lastAccess = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModificationTime(Date date) {
|
||||
lastMod = date;
|
||||
}
|
||||
|
||||
public void setLocationChanged(Date date) {
|
||||
public void setLocationChanged(PwDate date) {
|
||||
parentGroupLastMod = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsageCount() {
|
||||
return usageCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsageCount(long count) {
|
||||
usageCount = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpires(boolean exp) {
|
||||
expires = exp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroup prt) {
|
||||
parent = (PwGroupV4) prt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAddEntryIfIsRoot() {
|
||||
return true;
|
||||
@@ -217,14 +146,70 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
|
||||
@Override
|
||||
public PwIcon getIcon() {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
||||
return super.getIcon();
|
||||
} else {
|
||||
return customIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSearchEnabled() {
|
||||
|
||||
public void putCustomData(String key, String value) {
|
||||
customData.put(key, value);
|
||||
}
|
||||
|
||||
public boolean containsCustomData() {
|
||||
return customData.size() > 0;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public boolean isExpanded() {
|
||||
return isExpanded;
|
||||
}
|
||||
|
||||
public void setExpanded(boolean expanded) {
|
||||
isExpanded = expanded;
|
||||
}
|
||||
|
||||
public String getDefaultAutoTypeSequence() {
|
||||
return defaultAutoTypeSequence;
|
||||
}
|
||||
|
||||
public void setDefaultAutoTypeSequence(String defaultAutoTypeSequence) {
|
||||
this.defaultAutoTypeSequence = defaultAutoTypeSequence;
|
||||
}
|
||||
|
||||
public Boolean getEnableAutoType() {
|
||||
return enableAutoType;
|
||||
}
|
||||
|
||||
public void setEnableAutoType(Boolean enableAutoType) {
|
||||
this.enableAutoType = enableAutoType;
|
||||
}
|
||||
|
||||
public Boolean getEnableSearching() {
|
||||
return enableSearching;
|
||||
}
|
||||
|
||||
public void setEnableSearching(Boolean enableSearching) {
|
||||
this.enableSearching = enableSearching;
|
||||
}
|
||||
|
||||
public UUID getLastTopVisibleEntry() {
|
||||
return lastTopVisibleEntry;
|
||||
}
|
||||
|
||||
public void setLastTopVisibleEntry(UUID lastTopVisibleEntry) {
|
||||
this.lastTopVisibleEntry = lastTopVisibleEntry;
|
||||
}
|
||||
|
||||
public boolean isSearchEnabled() {
|
||||
PwGroupV4 group = this;
|
||||
while (group != null) {
|
||||
Boolean search = group.enableSearching;
|
||||
|
||||
@@ -3,12 +3,8 @@ package com.keepassdroid.database;
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class PwIcon implements Serializable {
|
||||
|
||||
|
||||
public boolean isMetaStreamIcon() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void writeBytes() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.keepassdroid.database;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PwIconCustom extends PwIcon {
|
||||
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabaseV4.UUID_ZERO, new byte[0]);
|
||||
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
|
||||
|
||||
public final UUID uuid;
|
||||
public byte[] imageData;
|
||||
@@ -32,6 +32,11 @@ public class PwIconCustom extends PwIcon {
|
||||
imageData = data;
|
||||
}
|
||||
|
||||
public PwIconCustom(PwIconCustom icon) {
|
||||
uuid = icon.uuid;
|
||||
imageData = icon.imageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
||||
@@ -32,6 +32,10 @@ public class PwIconStandard extends PwIcon {
|
||||
this.iconId = iconId;
|
||||
}
|
||||
|
||||
public PwIconStandard(PwIconStandard icon) {
|
||||
this.iconId = icon.iconId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMetaStreamIcon() {
|
||||
return iconId == 0;
|
||||
|
||||
@@ -21,12 +21,42 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.keepassdroid.database.PwDate.NEVER_EXPIRE;
|
||||
import static com.keepassdroid.database.PwDate.PW_NEVER_EXPIRE;
|
||||
|
||||
/**
|
||||
* Abstract class who manage Groups and Entries
|
||||
*/
|
||||
public abstract class PwNode implements Serializable {
|
||||
public abstract class PwNode implements ISmallTimeLogger, Serializable {
|
||||
|
||||
protected PwIconStandard icon = PwIconStandard.FIRST;
|
||||
|
||||
protected PwDate creation = new PwDate();
|
||||
protected PwDate lastMod = new PwDate();
|
||||
protected PwDate lastAccess = new PwDate();
|
||||
protected PwDate expireDate = new PwDate(NEVER_EXPIRE);
|
||||
|
||||
protected void construct() {
|
||||
}
|
||||
|
||||
public void assign(PwNode source) {
|
||||
this.icon = source.icon;
|
||||
|
||||
this.creation = source.creation;
|
||||
this.lastMod = source.lastMod;
|
||||
this.lastAccess = source.lastAccess;
|
||||
this.expireDate = source.expireDate;
|
||||
}
|
||||
|
||||
protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
|
||||
newEntry.icon = new PwIconStandard(this.icon);
|
||||
|
||||
newEntry.creation = creation.clone();
|
||||
newEntry.lastMod = lastMod.clone();
|
||||
newEntry.lastAccess = lastAccess.clone();
|
||||
newEntry.expireDate = expireDate.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of available Nodes
|
||||
@@ -48,12 +78,17 @@ public abstract class PwNode implements Serializable {
|
||||
/**
|
||||
* @return Visual icon
|
||||
*/
|
||||
public abstract PwIcon getIcon();
|
||||
public PwIcon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Creation date and time of the node
|
||||
*/
|
||||
public abstract Date getCreationTime();
|
||||
public PwIconStandard getIconStandard() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(PwIconStandard icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parent node
|
||||
@@ -66,6 +101,48 @@ public abstract class PwNode implements Serializable {
|
||||
*/
|
||||
public abstract void setParent(PwGroup parent);
|
||||
|
||||
public PwDate getCreationTime() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
public void setCreationTime(PwDate date) {
|
||||
creation = date;
|
||||
}
|
||||
|
||||
public PwDate getLastModificationTime() {
|
||||
return lastMod;
|
||||
}
|
||||
|
||||
public void setLastModificationTime(PwDate date) {
|
||||
lastMod = date;
|
||||
}
|
||||
|
||||
public PwDate getLastAccessTime() {
|
||||
return lastAccess;
|
||||
}
|
||||
|
||||
public void setLastAccessTime(PwDate date) {
|
||||
lastAccess = date;
|
||||
}
|
||||
|
||||
public PwDate getExpiryTime() {
|
||||
return expireDate;
|
||||
}
|
||||
|
||||
public void setExpiryTime(PwDate date) {
|
||||
expireDate = date;
|
||||
}
|
||||
|
||||
public void setExpires(boolean expires) {
|
||||
if (!expires) {
|
||||
expireDate = PW_NEVER_EXPIRE;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean expires() {
|
||||
return ! PwDate.IsSameDate(NEVER_EXPIRE, expireDate.getDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the content (type, title, icon) is visually the same
|
||||
* @param o Node to compare
|
||||
|
||||
@@ -47,14 +47,6 @@ public enum SortNodeEnum {
|
||||
boolean ascending;
|
||||
boolean groupsBefore;
|
||||
|
||||
NodeComparator() {
|
||||
this(true, true);
|
||||
}
|
||||
|
||||
NodeComparator(boolean groupsBefore) {
|
||||
this(true, groupsBefore);
|
||||
}
|
||||
|
||||
NodeComparator(boolean ascending, boolean groupsBefore) {
|
||||
this.ascending = ascending;
|
||||
this.groupsBefore = groupsBefore;
|
||||
@@ -66,14 +58,6 @@ public enum SortNodeEnum {
|
||||
*/
|
||||
public static class NodeTitleComparator extends NodeComparator {
|
||||
|
||||
public NodeTitleComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NodeTitleComparator(boolean groupsBefore) {
|
||||
super(groupsBefore);
|
||||
}
|
||||
|
||||
public NodeTitleComparator(boolean ascending, boolean groupsBefore) {
|
||||
super(ascending, groupsBefore);
|
||||
}
|
||||
@@ -84,7 +68,7 @@ public enum SortNodeEnum {
|
||||
|
||||
if (object1 instanceof PwGroup) {
|
||||
if (object2 instanceof PwGroup) {
|
||||
return new PwGroup.GroupNameComparator(ascending)
|
||||
return new GroupNameComparator(ascending)
|
||||
.compare((PwGroup) object1, (PwGroup) object2);
|
||||
} else if (object2 instanceof PwEntry) {
|
||||
if(groupsBefore)
|
||||
@@ -96,7 +80,7 @@ public enum SortNodeEnum {
|
||||
}
|
||||
} else if (object1 instanceof PwEntry) {
|
||||
if(object2 instanceof PwEntry) {
|
||||
return new PwEntry.EntryNameComparator(ascending)
|
||||
return new EntryNameComparator(ascending)
|
||||
.compare((PwEntry) object1, (PwEntry) object2);
|
||||
} else if (object2 instanceof PwGroup) {
|
||||
if(groupsBefore)
|
||||
@@ -121,14 +105,6 @@ public enum SortNodeEnum {
|
||||
*/
|
||||
public static class NodeCreationComparator extends NodeComparator {
|
||||
|
||||
public NodeCreationComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NodeCreationComparator(boolean groupsBefore) {
|
||||
super(groupsBefore);
|
||||
}
|
||||
|
||||
|
||||
public NodeCreationComparator(boolean ascending, boolean groupsBefore) {
|
||||
super(ascending, groupsBefore);
|
||||
@@ -141,7 +117,7 @@ public enum SortNodeEnum {
|
||||
|
||||
if (object1 instanceof PwGroup) {
|
||||
if (object2 instanceof PwGroup) {
|
||||
return new PwGroup.GroupCreationComparator(ascending)
|
||||
return new GroupCreationComparator(ascending)
|
||||
.compare((PwGroup) object1, (PwGroup) object2);
|
||||
} else if (object2 instanceof PwEntry) {
|
||||
if(groupsBefore)
|
||||
@@ -153,7 +129,7 @@ public enum SortNodeEnum {
|
||||
}
|
||||
} else if (object1 instanceof PwEntry) {
|
||||
if(object2 instanceof PwEntry) {
|
||||
return new PwEntry.EntryCreationComparator(ascending)
|
||||
return new EntryCreationComparator(ascending)
|
||||
.compare((PwEntry) object1, (PwEntry) object2);
|
||||
} else if (object2 instanceof PwGroup) {
|
||||
if(groupsBefore)
|
||||
@@ -164,8 +140,8 @@ public enum SortNodeEnum {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int nodeCreationComp = object1.getCreationTime()
|
||||
.compareTo(object2.getCreationTime());
|
||||
int nodeCreationComp = object1.getCreationTime().getDate()
|
||||
.compareTo(object2.getCreationTime().getDate());
|
||||
// If same creation, can be different
|
||||
if (nodeCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
@@ -173,4 +149,118 @@ public enum SortNodeEnum {
|
||||
return nodeCreationComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupNameComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupNameComp = object1.getName().compareToIgnoreCase(object2.getName());
|
||||
// If same name, can be different
|
||||
if (groupNameComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupNameComp = -groupNameComp;
|
||||
|
||||
return groupNameComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupCreationComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupCreationComp = object1.getCreationTime().getDate()
|
||||
.compareTo(object2.getCreationTime().getDate());
|
||||
// If same creation, can be different
|
||||
if (groupCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupCreationComp = -groupCreationComp;
|
||||
|
||||
return groupCreationComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Name
|
||||
*/
|
||||
public static class EntryNameComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryTitleComp = object1.getTitle().compareToIgnoreCase(object2.getTitle());
|
||||
// If same title, can be different
|
||||
if (entryTitleComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryTitleComp = -entryTitleComp;
|
||||
|
||||
return entryTitleComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Creation
|
||||
*/
|
||||
public static class EntryCreationComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryCreationComp = object1.getCreationTime().getDate()
|
||||
.compareTo(object2.getCreationTime().getDate());
|
||||
// If same creation, can be different
|
||||
if (entryCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryCreationComp = -entryCreationComp;
|
||||
|
||||
return entryCreationComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class AddGroup extends RunnableOnFinish {
|
||||
// Generate new group
|
||||
mGroup = pm.createGroup();
|
||||
mGroup.initNewGroup(mName, pm.newGroupId());
|
||||
mGroup.icon = mDb.pm.iconFactory.getIcon(mIconID);
|
||||
mGroup.setIcon(mDb.pm.iconFactory.getIcon(mIconID));
|
||||
pm.addGroupTo(mGroup, mParent);
|
||||
|
||||
// Commit to disk
|
||||
|
||||
@@ -72,14 +72,14 @@ public class DeleteGroup extends RunnableOnFinish {
|
||||
else {
|
||||
// TODO tests
|
||||
// Remove child entries
|
||||
List<PwEntry> childEnt = new ArrayList<>(mGroup.childEntries);
|
||||
List<PwEntry> childEnt = new ArrayList<>(mGroup.getChildEntries());
|
||||
for ( int i = 0; i < childEnt.size(); i++ ) {
|
||||
DeleteEntry task = new DeleteEntry(mContext, mDb, childEnt.get(i), null, true);
|
||||
task.run();
|
||||
}
|
||||
|
||||
// Remove child groups
|
||||
List<PwGroup> childGrp = new ArrayList<>(mGroup.childGroups);
|
||||
List<PwGroup> childGrp = new ArrayList<>(mGroup.getChildGroups());
|
||||
for ( int i = 0; i < childGrp.size(); i++ ) {
|
||||
DeleteGroup task = new DeleteGroup(mContext, mDb, childGrp.get(i), null, true);
|
||||
task.run();
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.net.Uri;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.dialogs.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class UpdateEntry extends RunnableOnFinish {
|
||||
|
||||
// Keep backup of original values in case save fails
|
||||
PwEntry backup;
|
||||
backup = (PwEntry) mOldE.clone();
|
||||
backup = mOldE.clone();
|
||||
|
||||
mFinish = new AfterUpdate(backup, finish);
|
||||
}
|
||||
|
||||
@@ -340,31 +340,31 @@ public class ImporterV3 extends Importer {
|
||||
// Ignore field
|
||||
break;
|
||||
case 0x0001 :
|
||||
grp.groupId = LEDataInputStream.readInt(buf, offset);
|
||||
grp.setGroupId(LEDataInputStream.readInt(buf, offset));
|
||||
break;
|
||||
case 0x0002 :
|
||||
grp.name = Types.readCString(buf, offset);
|
||||
grp.setName(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0003 :
|
||||
grp.tCreation = new PwDate(buf, offset);
|
||||
grp.setCreationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0004 :
|
||||
grp.tLastMod = new PwDate(buf, offset);
|
||||
grp.setLastModificationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0005 :
|
||||
grp.tLastAccess = new PwDate(buf, offset);
|
||||
grp.setLastAccessTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0006 :
|
||||
grp.tExpire = new PwDate(buf, offset);
|
||||
grp.setExpiryTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0007 :
|
||||
grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset));
|
||||
grp.setIcon(db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset)));
|
||||
break;
|
||||
case 0x0008 :
|
||||
grp.level = LEDataInputStream.readUShort(buf, offset);
|
||||
grp.setLevel(LEDataInputStream.readUShort(buf, offset));
|
||||
break;
|
||||
case 0x0009 :
|
||||
grp.flags = LEDataInputStream.readInt(buf, offset);
|
||||
grp.setFlags(LEDataInputStream.readInt(buf, offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -387,7 +387,7 @@ public class ImporterV3 extends Importer {
|
||||
ent.setUUID(Types.bytestoUUID(buf, offset));
|
||||
break;
|
||||
case 0x0002 :
|
||||
ent.groupId = LEDataInputStream.readInt(buf, offset);
|
||||
ent.setGroupId(LEDataInputStream.readInt(buf, offset));
|
||||
break;
|
||||
case 0x0003 :
|
||||
int iconId = LEDataInputStream.readInt(buf, offset);
|
||||
@@ -397,37 +397,37 @@ public class ImporterV3 extends Importer {
|
||||
iconId = 0;
|
||||
}
|
||||
|
||||
ent.icon = db.iconFactory.getIcon(iconId);
|
||||
ent.setIcon(db.iconFactory.getIcon(iconId));
|
||||
break;
|
||||
case 0x0004 :
|
||||
ent.title = Types.readCString(buf, offset);
|
||||
ent.setTitle(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0005 :
|
||||
ent.url = Types.readCString(buf, offset);
|
||||
ent.setUrl(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0006 :
|
||||
ent.username = Types.readCString(buf, offset);
|
||||
ent.setUsername(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0007 :
|
||||
ent.setPassword(buf, offset, Types.strlen(buf, offset));
|
||||
break;
|
||||
case 0x0008 :
|
||||
ent.additional = Types.readCString(buf, offset);
|
||||
ent.setNotes(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0009 :
|
||||
ent.tCreation = new PwDate(buf, offset);
|
||||
ent.setCreationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000A :
|
||||
ent.tLastMod = new PwDate(buf, offset);
|
||||
ent.setLastModificationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000B :
|
||||
ent.tLastAccess = new PwDate(buf, offset);
|
||||
ent.setLastAccessTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000C :
|
||||
ent.tExpire = new PwDate(buf, offset);
|
||||
ent.setExpiryTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000D :
|
||||
ent.binaryDesc = Types.readCString(buf, offset);
|
||||
ent.setBinaryDesc(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x000E :
|
||||
ent.setBinaryData(buf, offset, fieldSize);
|
||||
|
||||
@@ -46,6 +46,8 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDate;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||
@@ -308,7 +310,7 @@ public class ImporterV4 extends Importer {
|
||||
private boolean entryInHistory = false;
|
||||
private PwEntryV4 ctxHistoryBase = null;
|
||||
private PwDeletedObject ctxDeletedObject = null;
|
||||
private UUID customIconID = PwDatabaseV4.UUID_ZERO;
|
||||
private UUID customIconID = PwDatabase.UUID_ZERO;
|
||||
private byte[] customIconData;
|
||||
private String customDataKey = null;
|
||||
private String customDataValue = null;
|
||||
@@ -557,38 +559,38 @@ public class ImporterV4 extends Importer {
|
||||
|
||||
case Group:
|
||||
if ( name.equalsIgnoreCase(ElemUuid) ) {
|
||||
ctxGroup.uuid = ReadUuid(xpp);
|
||||
ctxGroup.setUUID(ReadUuid(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemName) ) {
|
||||
ctxGroup.name = ReadString(xpp);
|
||||
ctxGroup.setName(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemNotes) ) {
|
||||
ctxGroup.notes = ReadString(xpp);
|
||||
ctxGroup.setNotes(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemIcon) ) {
|
||||
ctxGroup.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0));
|
||||
ctxGroup.setIcon(db.iconFactory.getIcon((int)ReadUInt(xpp, 0)));
|
||||
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
|
||||
ctxGroup.customIcon = db.iconFactory.getIcon(ReadUuid(xpp));
|
||||
ctxGroup.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
|
||||
} else if ( name.equalsIgnoreCase(ElemTimes) ) {
|
||||
return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
|
||||
} else if ( name.equalsIgnoreCase(ElemIsExpanded) ) {
|
||||
ctxGroup.isExpanded = ReadBool(xpp, true);
|
||||
ctxGroup.setExpanded(ReadBool(xpp, true));
|
||||
} else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) {
|
||||
ctxGroup.defaultAutoTypeSequence = ReadString(xpp);
|
||||
ctxGroup.setDefaultAutoTypeSequence(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) {
|
||||
ctxGroup.enableAutoType = StringToBoolean(ReadString(xpp));
|
||||
ctxGroup.setEnableAutoType(StringToBoolean(ReadString(xpp)));
|
||||
} else if ( name.equalsIgnoreCase(ElemEnableSearching) ) {
|
||||
ctxGroup.enableSearching = StringToBoolean(ReadString(xpp));
|
||||
ctxGroup.setEnableSearching(StringToBoolean(ReadString(xpp)));
|
||||
} else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) {
|
||||
ctxGroup.lastTopVisibleEntry = ReadUuid(xpp);
|
||||
ctxGroup.setLastTopVisibleEntry(ReadUuid(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemCustomData) ) {
|
||||
return SwitchContext(ctx, KdbContext.GroupCustomData, xpp);
|
||||
} else if ( name.equalsIgnoreCase(ElemGroup) ) {
|
||||
ctxGroup = new PwGroupV4();
|
||||
ctxGroups.peek().AddGroup(ctxGroup, true);
|
||||
ctxGroups.peek().AddGroup(ctxGroup);
|
||||
ctxGroups.push(ctxGroup);
|
||||
|
||||
return SwitchContext(ctx, KdbContext.Group, xpp);
|
||||
} else if ( name.equalsIgnoreCase(ElemEntry) ) {
|
||||
ctxEntry = new PwEntryV4();
|
||||
ctxGroup.AddEntry(ctxEntry, true);
|
||||
ctxGroup.AddEntry(ctxEntry);
|
||||
|
||||
entryInHistory = false;
|
||||
return SwitchContext(ctx, KdbContext.Entry, xpp);
|
||||
@@ -618,17 +620,17 @@ public class ImporterV4 extends Importer {
|
||||
if ( name.equalsIgnoreCase(ElemUuid) ) {
|
||||
ctxEntry.setUUID(ReadUuid(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemIcon) ) {
|
||||
ctxEntry.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0));
|
||||
ctxEntry.setIcon(db.iconFactory.getIcon((int)ReadUInt(xpp, 0)));
|
||||
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
|
||||
ctxEntry.customIcon = db.iconFactory.getIcon(ReadUuid(xpp));
|
||||
ctxEntry.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
|
||||
} else if ( name.equalsIgnoreCase(ElemFgColor) ) {
|
||||
ctxEntry.foregroundColor = ReadString(xpp);
|
||||
ctxEntry.setForegroundColor(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemBgColor) ) {
|
||||
ctxEntry.backgroupColor = ReadString(xpp);
|
||||
ctxEntry.setBackgroupColor(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) {
|
||||
ctxEntry.overrideURL = ReadString(xpp);
|
||||
ctxEntry.setOverrideURL(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemTags) ) {
|
||||
ctxEntry.tags = ReadString(xpp);
|
||||
ctxEntry.setTags(ReadString(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemTimes) ) {
|
||||
return SwitchContext(ctx, KdbContext.EntryTimes, xpp);
|
||||
} else if ( name.equalsIgnoreCase(ElemString) ) {
|
||||
@@ -679,19 +681,19 @@ public class ImporterV4 extends Importer {
|
||||
}
|
||||
|
||||
if ( name.equalsIgnoreCase(ElemLastModTime) ) {
|
||||
tl.setLastModificationTime(ReadTime(xpp));
|
||||
tl.setLastModificationTime(ReadPwTime(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemCreationTime) ) {
|
||||
tl.setCreationTime(ReadTime(xpp));
|
||||
tl.setCreationTime(ReadPwTime(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) {
|
||||
tl.setLastAccessTime(ReadTime(xpp));
|
||||
tl.setLastAccessTime(ReadPwTime(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemExpiryTime) ) {
|
||||
tl.setExpiryTime(ReadTime(xpp));
|
||||
tl.setExpiryTime(ReadPwTime(xpp));
|
||||
} else if ( name.equalsIgnoreCase(ElemExpires) ) {
|
||||
tl.setExpires(ReadBool(xpp, false));
|
||||
} else if ( name.equalsIgnoreCase(ElemUsageCount) ) {
|
||||
tl.setUsageCount(ReadULong(xpp, 0));
|
||||
} else if ( name.equalsIgnoreCase(ElemLocationChanged) ) {
|
||||
tl.setLocationChanged(ReadTime(xpp));
|
||||
tl.setLocationChanged(ReadPwTime(xpp));
|
||||
} else {
|
||||
ReadUnknown(xpp);
|
||||
}
|
||||
@@ -717,11 +719,11 @@ public class ImporterV4 extends Importer {
|
||||
|
||||
case EntryAutoType:
|
||||
if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) {
|
||||
ctxEntry.autoType.enabled = ReadBool(xpp, true);
|
||||
ctxEntry.getAutoType().enabled = ReadBool(xpp, true);
|
||||
} else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) {
|
||||
ctxEntry.autoType.obfuscationOptions = ReadUInt(xpp, 0);
|
||||
ctxEntry.getAutoType().obfuscationOptions = ReadUInt(xpp, 0);
|
||||
} else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) {
|
||||
ctxEntry.autoType.defaultSequence = ReadString(xpp);
|
||||
ctxEntry.getAutoType().defaultSequence = ReadString(xpp);
|
||||
} else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) {
|
||||
return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp);
|
||||
} else {
|
||||
@@ -742,7 +744,7 @@ public class ImporterV4 extends Importer {
|
||||
case EntryHistory:
|
||||
if ( name.equalsIgnoreCase(ElemEntry) ) {
|
||||
ctxEntry = new PwEntryV4();
|
||||
ctxHistoryBase.history.add(ctxEntry);
|
||||
ctxHistoryBase.addToHistory(ctxEntry);
|
||||
|
||||
entryInHistory = true;
|
||||
return SwitchContext(ctx, KdbContext.Entry, xpp);
|
||||
@@ -795,13 +797,13 @@ public class ImporterV4 extends Importer {
|
||||
} else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) {
|
||||
return KdbContext.Meta;
|
||||
} else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) {
|
||||
if ( ! customIconID.equals(PwDatabaseV4.UUID_ZERO) ) {
|
||||
if ( ! customIconID.equals(PwDatabase.UUID_ZERO) ) {
|
||||
PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
|
||||
db.customIcons.add(icon);
|
||||
db.iconFactory.put(icon);
|
||||
} else assert(false);
|
||||
|
||||
customIconID = PwDatabaseV4.UUID_ZERO;
|
||||
customIconID = PwDatabase.UUID_ZERO;
|
||||
customIconData = null;
|
||||
|
||||
return KdbContext.CustomIcons;
|
||||
@@ -819,8 +821,8 @@ public class ImporterV4 extends Importer {
|
||||
|
||||
return KdbContext.CustomData;
|
||||
} else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) {
|
||||
if ( ctxGroup.uuid == null || ctxGroup.uuid.equals(PwDatabaseV4.UUID_ZERO) ) {
|
||||
ctxGroup.uuid = UUID.randomUUID();
|
||||
if ( ctxGroup.getUUID() == null || ctxGroup.getUUID().equals(PwDatabase.UUID_ZERO) ) {
|
||||
ctxGroup.setUUID(UUID.randomUUID());
|
||||
}
|
||||
|
||||
ctxGroups.pop();
|
||||
@@ -838,7 +840,7 @@ public class ImporterV4 extends Importer {
|
||||
return KdbContext.Group;
|
||||
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
|
||||
if (groupCustomDataKey != null && groupCustomDataValue != null) {
|
||||
ctxGroup.customData.put(groupCustomDataKey, groupCustomDataKey);
|
||||
ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -849,8 +851,8 @@ public class ImporterV4 extends Importer {
|
||||
return KdbContext.GroupCustomData;
|
||||
|
||||
} else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) {
|
||||
if ( ctxEntry.uuid == null || ctxEntry.uuid.equals(PwDatabaseV4.UUID_ZERO) ) {
|
||||
ctxEntry.uuid = UUID.randomUUID();
|
||||
if ( ctxEntry.getUUID() == null || ctxEntry.getUUID().equals(PwDatabase.UUID_ZERO) ) {
|
||||
ctxEntry.setUUID(UUID.randomUUID());
|
||||
}
|
||||
|
||||
if ( entryInHistory ) {
|
||||
@@ -868,7 +870,7 @@ public class ImporterV4 extends Importer {
|
||||
|
||||
return KdbContext.Entry;
|
||||
} else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) {
|
||||
ctxEntry.binaries.put(ctxBinaryName, ctxBinaryValue);
|
||||
ctxEntry.putProtectedBinary(ctxBinaryName, ctxBinaryValue);
|
||||
ctxBinaryName = null;
|
||||
ctxBinaryValue = null;
|
||||
|
||||
@@ -876,7 +878,7 @@ public class ImporterV4 extends Importer {
|
||||
} else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) {
|
||||
return KdbContext.Entry;
|
||||
} else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) {
|
||||
ctxEntry.autoType.put(ctxATName, ctxATSeq);
|
||||
ctxEntry.getAutoType().put(ctxATName, ctxATSeq);
|
||||
ctxATName = null;
|
||||
ctxATSeq = null;
|
||||
|
||||
@@ -885,7 +887,7 @@ public class ImporterV4 extends Importer {
|
||||
return KdbContext.Entry;
|
||||
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
|
||||
if (entryCustomDataKey != null && entryCustomDataValue != null) {
|
||||
ctxEntry.customData.put(entryCustomDataKey, entryCustomDataValue);
|
||||
ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -912,6 +914,10 @@ public class ImporterV4 extends Importer {
|
||||
throw new RuntimeException("Invalid end element: Context " + contextName + "End element: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private PwDate ReadPwTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
||||
return new PwDate(ReadTime(xpp));
|
||||
}
|
||||
|
||||
private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
||||
String sDate = ReadString(xpp);
|
||||
@@ -980,7 +986,7 @@ public class ImporterV4 extends Importer {
|
||||
String encoded = ReadString(xpp);
|
||||
|
||||
if (encoded == null || encoded.length() == 0 ) {
|
||||
return PwDatabaseV4.UUID_ZERO;
|
||||
return PwDatabase.UUID_ZERO;
|
||||
}
|
||||
|
||||
// TODO: Switch to framework Base64 once API level 8 is the minimum
|
||||
|
||||
@@ -261,7 +261,7 @@ public class PwDbV3Output extends PwDbOutput {
|
||||
|
||||
// Recurse over children
|
||||
for ( int i = 0; i < group.numbersOfChildGroups(); i++ ) {
|
||||
sortGroup((PwGroupV3) group.childGroups.get(i), groupList);
|
||||
sortGroup((PwGroupV3) group.getChildGroupAt(i), groupList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import com.keepassdroid.database.PwDefsV4;
|
||||
import com.keepassdroid.database.PwDeletedObject;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwEntryV4.AutoType;
|
||||
import com.keepassdroid.database.AutoType;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
import com.keepassdroid.database.PwIconCustom;
|
||||
@@ -245,7 +245,7 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
while(true) {
|
||||
try {
|
||||
if (group.parent == groupStack.peek()) {
|
||||
if (group.getParent() == groupStack.peek()) {
|
||||
groupStack.push(group);
|
||||
startGroup(group);
|
||||
break;
|
||||
@@ -434,21 +434,21 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
private void startGroup(PwGroupV4 group) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
xml.startTag(null, ElemGroup);
|
||||
writeObject(ElemUuid, group.uuid);
|
||||
writeObject(ElemName, group.name);
|
||||
writeObject(ElemNotes, group.notes);
|
||||
writeObject(ElemIcon, group.icon.iconId);
|
||||
writeObject(ElemUuid, group.getUUID());
|
||||
writeObject(ElemName, group.getName());
|
||||
writeObject(ElemNotes, group.getNotes());
|
||||
writeObject(ElemIcon, group.getIconStandard().iconId);
|
||||
|
||||
if (!group.customIcon.equals(PwIconCustom.ZERO)) {
|
||||
writeObject(ElemCustomIconID, group.customIcon.uuid);
|
||||
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
||||
writeObject(ElemCustomIconID, group.getCustomIcon().uuid);
|
||||
}
|
||||
|
||||
writeList(ElemTimes, group);
|
||||
writeObject(ElemIsExpanded, group.isExpanded);
|
||||
writeObject(ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence);
|
||||
writeObject(ElemEnableAutoType, group.enableAutoType);
|
||||
writeObject(ElemEnableSearching, group.enableSearching);
|
||||
writeObject(ElemLastTopVisibleEntry, group.lastTopVisibleEntry);
|
||||
writeObject(ElemIsExpanded, group.isExpanded());
|
||||
writeObject(ElemGroupDefaultAutoTypeSeq, group.getDefaultAutoTypeSequence());
|
||||
writeObject(ElemEnableAutoType, group.getEnableAutoType());
|
||||
writeObject(ElemEnableSearching, group.getEnableSearching());
|
||||
writeObject(ElemLastTopVisibleEntry, group.getLastTopVisibleEntry());
|
||||
|
||||
}
|
||||
|
||||
@@ -461,28 +461,28 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
xml.startTag(null, ElemEntry);
|
||||
|
||||
writeObject(ElemUuid, entry.uuid);
|
||||
writeObject(ElemIcon, entry.icon.iconId);
|
||||
writeObject(ElemUuid, entry.getUUID());
|
||||
writeObject(ElemIcon, entry.getIconStandard().iconId);
|
||||
|
||||
if (!entry.customIcon.equals(PwIconCustom.ZERO)) {
|
||||
writeObject(ElemCustomIconID, entry.customIcon.uuid);
|
||||
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
||||
writeObject(ElemCustomIconID, entry.getCustomIcon().uuid);
|
||||
}
|
||||
|
||||
writeObject(ElemFgColor, entry.foregroundColor);
|
||||
writeObject(ElemBgColor, entry.backgroupColor);
|
||||
writeObject(ElemOverrideUrl, entry.overrideURL);
|
||||
writeObject(ElemTags, entry.tags);
|
||||
writeObject(ElemFgColor, entry.getForegroundColor());
|
||||
writeObject(ElemBgColor, entry.getBackgroupColor());
|
||||
writeObject(ElemOverrideUrl, entry.getOverrideURL());
|
||||
writeObject(ElemTags, entry.getTags());
|
||||
|
||||
writeList(ElemTimes, entry);
|
||||
|
||||
writeList(entry.getFields(), true);
|
||||
writeList(entry.binaries);
|
||||
writeList(ElemAutoType, entry.autoType);
|
||||
writeList(entry.getBinaries());
|
||||
writeList(ElemAutoType, entry.getAutoType());
|
||||
|
||||
if (!isHistory) {
|
||||
writeList(ElemHistory, entry.history, true);
|
||||
writeList(ElemHistory, entry.getHistory(), true);
|
||||
} else {
|
||||
assert(entry.history.size() == 0);
|
||||
assert(entry.sizeOfHistory() == 0);
|
||||
}
|
||||
|
||||
xml.endTag(null, ElemEntry);
|
||||
@@ -755,13 +755,13 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
xml.startTag(null, name);
|
||||
|
||||
writeObject(ElemLastModTime, it.getLastModificationTime());
|
||||
writeObject(ElemCreationTime, it.getCreationTime());
|
||||
writeObject(ElemLastAccessTime, it.getLastAccessTime());
|
||||
writeObject(ElemExpiryTime, it.getExpiryTime());
|
||||
writeObject(ElemLastModTime, it.getLastModificationTime().getDate());
|
||||
writeObject(ElemCreationTime, it.getCreationTime().getDate());
|
||||
writeObject(ElemLastAccessTime, it.getLastAccessTime().getDate());
|
||||
writeObject(ElemExpiryTime, it.getExpiryTime().getDate());
|
||||
writeObject(ElemExpires, it.expires());
|
||||
writeObject(ElemUsageCount, it.getUsageCount());
|
||||
writeObject(ElemLocationChanged, it.getLocationChanged());
|
||||
writeObject(ElemLocationChanged, it.getLocationChanged().getDate());
|
||||
|
||||
xml.endTag(null, name);
|
||||
}
|
||||
|
||||
@@ -79,27 +79,27 @@ public class PwEntryOutputV3 {
|
||||
// Group ID
|
||||
mOS.write(GROUPID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.groupId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getGroupId()));
|
||||
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.icon.iconId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
|
||||
|
||||
// Title
|
||||
//byte[] title = mPE.title.getBytes("UTF-8");
|
||||
mOS.write(TITLE_FIELD_TYPE);
|
||||
int titleLen = Types.writeCString(mPE.title, mOS);
|
||||
int titleLen = Types.writeCString(mPE.getTitle(), mOS);
|
||||
outputBytes += titleLen;
|
||||
|
||||
// URL
|
||||
mOS.write(URL_FIELD_TYPE);
|
||||
int urlLen = Types.writeCString(mPE.url, mOS);
|
||||
int urlLen = Types.writeCString(mPE.getUrl(), mOS);
|
||||
outputBytes += urlLen;
|
||||
|
||||
// Username
|
||||
mOS.write(USERNAME_FIELD_TYPE);
|
||||
int userLen = Types.writeCString(mPE.username, mOS);
|
||||
int userLen = Types.writeCString(mPE.getUsername(), mOS);
|
||||
outputBytes += userLen;
|
||||
|
||||
// Password
|
||||
@@ -112,24 +112,24 @@ public class PwEntryOutputV3 {
|
||||
|
||||
// Additional
|
||||
mOS.write(ADDITIONAL_FIELD_TYPE);
|
||||
int addlLen = Types.writeCString(mPE.additional, mOS);
|
||||
int addlLen = Types.writeCString(mPE.getNotes(), mOS);
|
||||
outputBytes += addlLen;
|
||||
|
||||
// Create date
|
||||
writeDate(CREATE_FIELD_TYPE, mPE.tCreation.getCDate());
|
||||
writeDate(CREATE_FIELD_TYPE, mPE.getCreationTime().getCDate());
|
||||
|
||||
// Modification date
|
||||
writeDate(MOD_FIELD_TYPE, mPE.tLastMod.getCDate());
|
||||
writeDate(MOD_FIELD_TYPE, mPE.getLastModificationTime().getCDate());
|
||||
|
||||
// Access date
|
||||
writeDate(ACCESS_FIELD_TYPE, mPE.tLastAccess.getCDate());
|
||||
writeDate(ACCESS_FIELD_TYPE, mPE.getLastAccessTime().getCDate());
|
||||
|
||||
// Expiration date
|
||||
writeDate(EXPIRE_FIELD_TYPE, mPE.tExpire.getCDate());
|
||||
writeDate(EXPIRE_FIELD_TYPE, mPE.getExpiryTime().getCDate());
|
||||
|
||||
// Binary desc
|
||||
mOS.write(BINARY_DESC_FIELD_TYPE);
|
||||
int descLen = Types.writeCString(mPE.binaryDesc, mOS);
|
||||
int descLen = Types.writeCString(mPE.getBinaryDesc(), mOS);
|
||||
outputBytes += descLen;
|
||||
|
||||
// Binary data
|
||||
|
||||
@@ -65,46 +65,46 @@ public class PwGroupOutputV3 {
|
||||
// Group ID
|
||||
mOS.write(GROUPID_FIELD_TYPE);
|
||||
mOS.write(GROUPID_FIELD_SIZE);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.groupId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getGroupId()));
|
||||
|
||||
// Name
|
||||
mOS.write(NAME_FIELD_TYPE);
|
||||
Types.writeCString(mPG.name, mOS);
|
||||
Types.writeCString(mPG.getName(), mOS);
|
||||
|
||||
// Create date
|
||||
mOS.write(CREATE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(mPG.tCreation.getCDate());
|
||||
mOS.write(mPG.getCreationTime().getCDate());
|
||||
|
||||
// Modification date
|
||||
mOS.write(MOD_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(mPG.tLastMod.getCDate());
|
||||
mOS.write(mPG.getLastModificationTime().getCDate());
|
||||
|
||||
// Access date
|
||||
mOS.write(ACCESS_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(mPG.tLastAccess.getCDate());
|
||||
mOS.write(mPG.getLastAccessTime().getCDate());
|
||||
|
||||
// Expiration date
|
||||
mOS.write(EXPIRE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(mPG.tExpire.getCDate());
|
||||
mOS.write(mPG.getExpiryTime().getCDate());
|
||||
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(IMAGEID_FIELD_SIZE);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.icon.iconId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
|
||||
|
||||
// Level
|
||||
mOS.write(LEVEL_FIELD_TYPE);
|
||||
mOS.write(LEVEL_FIELD_SIZE);
|
||||
mOS.write(LEDataOutputStream.writeUShortBuf(mPG.level));
|
||||
mOS.write(LEDataOutputStream.writeUShortBuf(mPG.getLevel()));
|
||||
|
||||
// Flags
|
||||
mOS.write(FLAGS_FIELD_TYPE);
|
||||
mOS.write(FLAGS_FIELD_SIZE);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.flags));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getFlags()));
|
||||
|
||||
// End
|
||||
mOS.write(END_FIELD_TYPE);
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
*/
|
||||
package com.keepassdroid.database.security;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ProtectedBinary {
|
||||
public class ProtectedBinary implements Serializable {
|
||||
|
||||
public final static ProtectedBinary EMPTY = new ProtectedBinary();
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ public class ProtectedString implements Serializable {
|
||||
|
||||
public ProtectedString() {
|
||||
this(false, "");
|
||||
|
||||
}
|
||||
|
||||
public ProtectedString(boolean enableProtection, String string) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -39,7 +39,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.keepassdroid.fileselect.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class AssignMasterKeyDialogFragment extends DialogFragment {
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
@@ -17,18 +17,16 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
@@ -57,6 +55,7 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
private CompoundButton spaceBox;
|
||||
private CompoundButton specialsBox;
|
||||
private CompoundButton bracketsBox;
|
||||
private CompoundButton extendedBox;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -76,20 +75,21 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
root = inflater.inflate(R.layout.generate_password, null);
|
||||
|
||||
lengthTextView = (EditText) root.findViewById(R.id.length);
|
||||
lengthTextView = root.findViewById(R.id.length);
|
||||
|
||||
uppercaseBox = (CompoundButton) root.findViewById(R.id.cb_uppercase);
|
||||
lowercaseBox = (CompoundButton) root.findViewById(R.id.cb_lowercase);
|
||||
digitsBox = (CompoundButton) root.findViewById(R.id.cb_digits);
|
||||
minusBox = (CompoundButton) root.findViewById(R.id.cb_minus);
|
||||
underlineBox = (CompoundButton) root.findViewById(R.id.cb_underline);
|
||||
spaceBox = (CompoundButton) root.findViewById(R.id.cb_space);
|
||||
specialsBox = (CompoundButton) root.findViewById(R.id.cb_specials);
|
||||
bracketsBox = (CompoundButton) root.findViewById(R.id.cb_brackets);
|
||||
uppercaseBox = root.findViewById(R.id.cb_uppercase);
|
||||
lowercaseBox = root.findViewById(R.id.cb_lowercase);
|
||||
digitsBox = root.findViewById(R.id.cb_digits);
|
||||
minusBox = root.findViewById(R.id.cb_minus);
|
||||
underlineBox = root.findViewById(R.id.cb_underline);
|
||||
spaceBox = root.findViewById(R.id.cb_space);
|
||||
specialsBox = root.findViewById(R.id.cb_specials);
|
||||
bracketsBox = root.findViewById(R.id.cb_brackets);
|
||||
extendedBox = root.findViewById(R.id.cb_extended);
|
||||
|
||||
assignDefaultCharacters();
|
||||
|
||||
SeekBar seekBar = (SeekBar) root.findViewById(R.id.seekbar_length);
|
||||
SeekBar seekBar = root.findViewById(R.id.seekbar_length);
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
@@ -102,34 +102,25 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
seekBar.setProgress(PreferencesUtil.getDefaultPasswordLength(getContext().getApplicationContext()));
|
||||
seekBar.setProgress(PreferencesUtil.getDefaultPasswordLength(getContext()));
|
||||
|
||||
Button genPassButton = (Button) root.findViewById(R.id.generate_password_button);
|
||||
genPassButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
fillPassword();
|
||||
}
|
||||
});
|
||||
Button genPassButton = root.findViewById(R.id.generate_password_button);
|
||||
genPassButton.setOnClickListener(v -> fillPassword());
|
||||
|
||||
builder.setView(root)
|
||||
.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
EditText password = (EditText) root.findViewById(R.id.password);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
|
||||
mListener.acceptPassword(bundle);
|
||||
.setPositiveButton(R.string.accept, (dialog, id) -> {
|
||||
EditText password = root.findViewById(R.id.password);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
|
||||
mListener.acceptPassword(bundle);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Bundle bundle = new Bundle();
|
||||
mListener.cancelPassword(bundle);
|
||||
.setNegativeButton(R.string.cancel, (dialog, id) -> {
|
||||
Bundle bundle = new Bundle();
|
||||
mListener.cancelPassword(bundle);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
|
||||
// Pre-populate a password to possibly save the user a few clicks
|
||||
@@ -147,9 +138,10 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
spaceBox.setChecked(false);
|
||||
specialsBox.setChecked(false);
|
||||
bracketsBox.setChecked(false);
|
||||
extendedBox.setChecked(false);
|
||||
|
||||
Set<String> defaultPasswordChars =
|
||||
PreferencesUtil.getDefaultPasswordCharacters(getContext().getApplicationContext());
|
||||
PreferencesUtil.getDefaultPasswordCharacters(getContext());
|
||||
for(String passwordChar : defaultPasswordChars) {
|
||||
if (passwordChar.equals(getString(R.string.value_password_uppercase))) {
|
||||
uppercaseBox.setChecked(true);
|
||||
@@ -175,11 +167,14 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
else if (passwordChar.equals(getString(R.string.value_password_brackets))) {
|
||||
bracketsBox.setChecked(true);
|
||||
}
|
||||
else if (passwordChar.equals(getString(R.string.value_password_extended))) {
|
||||
extendedBox.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillPassword() {
|
||||
EditText txtPassword = (EditText) root.findViewById(R.id.password);
|
||||
EditText txtPassword = root.findViewById(R.id.password);
|
||||
txtPassword.setText(generatePassword());
|
||||
}
|
||||
|
||||
@@ -197,7 +192,8 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
underlineBox.isChecked(),
|
||||
spaceBox.isChecked(),
|
||||
specialsBox.isChecked(),
|
||||
bracketsBox.isChecked());
|
||||
bracketsBox.isChecked(),
|
||||
extendedBox.isChecked());
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), R.string.error_wrong_length, Toast.LENGTH_LONG).show();
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.dialog;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.dialog;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class UnavailableFeatureDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String MIN_REQUIRED_VERSION_ARG = "MIN_REQUIRED_VERSION_ARG";
|
||||
private int minVersionRequired = Build.VERSION_CODES.M;
|
||||
|
||||
public static UnavailableFeatureDialogFragment getInstance(int minVersionRequired) {
|
||||
UnavailableFeatureDialogFragment fragment = new UnavailableFeatureDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(MIN_REQUIRED_VERSION_ARG, minVersionRequired);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
if (getArguments() != null && getArguments().containsKey(MIN_REQUIRED_VERSION_ARG))
|
||||
minVersionRequired = getArguments().getInt(MIN_REQUIRED_VERSION_ARG);
|
||||
|
||||
assert getActivity() != null;
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View rootView = inflater.inflate(R.layout.unavailable_feature, null);
|
||||
TextView messageView = rootView.findViewById(R.id.unavailable_feature_message);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
SpannableStringBuilder message = new SpannableStringBuilder();
|
||||
message.append(getString(R.string.unavailable_feature_text))
|
||||
.append("\n");
|
||||
if(Build.VERSION.SDK_INT < minVersionRequired) {
|
||||
message.append(getString(R.string.unavailable_feature_version,
|
||||
androidNameFromApiNumber(Build.VERSION.SDK_INT),
|
||||
androidNameFromApiNumber(minVersionRequired)));
|
||||
message.append("\n\n")
|
||||
.append(Html.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>"));
|
||||
} else
|
||||
message.append(getString(R.string.unavailable_feature_hardware));
|
||||
|
||||
messageView.setText(message);
|
||||
messageView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
builder.setView(rootView)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> { });
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private String androidNameFromApiNumber(int apiNumber) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Field[] fields = Build.VERSION_CODES.class.getFields();
|
||||
for (Field field : fields) {
|
||||
String fieldName = field.getName();
|
||||
int fieldValue = -1;
|
||||
try {
|
||||
fieldValue = field.getInt(new Object());
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (fieldValue == apiNumber) {
|
||||
builder.append(fieldName).append(" ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
builder.append("(API ");
|
||||
builder.append(apiNumber).append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.dialog;
|
||||
package com.keepassdroid.dialogs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
@@ -20,69 +20,42 @@
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.utils.Util;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class BrowserDialog extends Dialog {
|
||||
|
||||
public BrowserDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
public class BrowserDialog extends DialogFragment {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.browser_install);
|
||||
setTitle(R.string.file_browser);
|
||||
|
||||
Button cancel = (Button) findViewById(R.id.cancel);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
BrowserDialog.this.cancel();
|
||||
}
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
// Get the layout inflater
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View root = inflater.inflate(R.layout.browser_install, null);
|
||||
builder.setView(root)
|
||||
.setNegativeButton(R.string.cancel, (dialog, id) -> { });
|
||||
|
||||
Button market = root.findViewById(R.id.install_market);
|
||||
market.setOnClickListener((view) -> {
|
||||
Util.gotoUrl(getContext(), R.string.filemanager_play_store);
|
||||
dismiss();
|
||||
});
|
||||
|
||||
Button market = (Button) findViewById(R.id.install_market);
|
||||
market.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
Util.gotoUrl(getContext(), R.string.oi_filemanager_market);
|
||||
BrowserDialog.this.cancel();
|
||||
}
|
||||
});
|
||||
if (!isMarketInstalled()) {
|
||||
market.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Button web = (Button) findViewById(R.id.install_web);
|
||||
web.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
Util.gotoUrl(getContext(), R.string.oi_filemanager_web);
|
||||
BrowserDialog.this.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isMarketInstalled() {
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
|
||||
try {
|
||||
pm.getPackageInfo("com.android.vending", 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
Button web = root.findViewById(R.id.install_web);
|
||||
web.setOnClickListener(view -> {
|
||||
Util.gotoUrl(getContext(), R.string.filemanager_f_droid);
|
||||
dismiss();
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ package com.keepassdroid.fileselect;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
@@ -49,17 +48,17 @@ import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.database.edit.CreateDB;
|
||||
import com.keepassdroid.database.edit.FileOnFinish;
|
||||
import com.keepassdroid.database.exception.ContentFileNotFoundException;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.CreateFileDialogFragment;
|
||||
import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.dialogs.CreateFileDialogFragment;
|
||||
import com.keepassdroid.password.AssignPasswordHelper;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.view.AssignPasswordHelper;
|
||||
import com.keepassdroid.view.FileNameView;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
@@ -103,6 +102,8 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
private KeyFileHelper keyFileHelper;
|
||||
|
||||
private String defaultPath;
|
||||
|
||||
public static void launch(Activity activity) {
|
||||
Intent intent = new Intent(activity, FileSelectActivity.class);
|
||||
// only to avoid visible flickering when redirecting
|
||||
@@ -142,11 +143,11 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
fileNameView = (FileNameView) findViewById(R.id.file_select);
|
||||
|
||||
// Set the initial value of the filename
|
||||
String defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
+ getString(R.string.database_file_path_default)
|
||||
+ getString(R.string.database_file_name_default)
|
||||
+ getString(R.string.database_file_extension_default);
|
||||
openFileNameView.setText(defaultPath);
|
||||
openFileNameView.setHint(defaultPath);
|
||||
|
||||
RecyclerView mListFiles = (RecyclerView) findViewById(R.id.file_list);
|
||||
mListFiles.setLayoutManager(new LinearLayoutManager(this));
|
||||
@@ -159,53 +160,24 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
// Open button
|
||||
View openButton = findViewById(R.id.open_database);
|
||||
openButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
String fileName = openFileNameView.getText().toString();
|
||||
try {
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
fileName,
|
||||
assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, fileName);
|
||||
}
|
||||
}
|
||||
catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found_content, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
openButton.setOnClickListener(v -> {
|
||||
String fileName = openFileNameView.getText().toString();
|
||||
if (fileName.isEmpty())
|
||||
fileName = defaultPath;
|
||||
launchPasswordActivityWithPath(fileName);
|
||||
});
|
||||
|
||||
// Create button
|
||||
View createButton = findViewById(R.id.create_database);
|
||||
createButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
createButton.setOnClickListener(v ->
|
||||
FileSelectActivityPermissionsDispatcher
|
||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this);
|
||||
}
|
||||
});
|
||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this)
|
||||
);
|
||||
|
||||
keyFileHelper = new KeyFileHelper(this);
|
||||
View browseButton = findViewById(R.id.browse_button);
|
||||
browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
||||
new KeyFileHelper.ClickDataUriCallback() {
|
||||
@Override
|
||||
public Uri onRequestIntentFilePicker() {
|
||||
return Uri.parse("file://" + openFileNameView.getText().toString());
|
||||
}
|
||||
}));
|
||||
() -> Uri.parse("file://" + openFileNameView.getText().toString())));
|
||||
|
||||
// Construct adapter with listeners
|
||||
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
|
||||
@@ -259,8 +231,18 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
// Delete flickering for kitkat <=
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
overridePendingTransition(0, 0);
|
||||
} catch (ContentFileNotFoundException e) {
|
||||
String error = getString(R.string.file_not_found_content);
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
error, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, error, e);
|
||||
} catch (FileNotFoundException e) {
|
||||
String error = getString(R.string.file_not_found);
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
error, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, error, e);
|
||||
} catch (Exception e) {
|
||||
// Ignore exception
|
||||
Log.e(TAG, "Can't launch PasswordActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,26 +363,32 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
String databaseFilename = databaseUri.getPath();
|
||||
try {
|
||||
String databaseFilename = databaseUri.getPath();
|
||||
|
||||
// Prep an object to collect a password once the database has
|
||||
// been created
|
||||
FileOnFinish launchActivityOnFinish = new FileOnFinish(
|
||||
new LaunchGroupActivity(databaseFilename));
|
||||
AssignPasswordOnFinish assignPasswordOnFinish =
|
||||
new AssignPasswordOnFinish(launchActivityOnFinish);
|
||||
// Prep an object to collect a password once the database has
|
||||
// been created
|
||||
FileOnFinish launchActivityOnFinish = new FileOnFinish(
|
||||
new LaunchGroupActivity(databaseFilename));
|
||||
AssignPasswordOnFinish assignPasswordOnFinish =
|
||||
new AssignPasswordOnFinish(launchActivityOnFinish);
|
||||
|
||||
// Create the new database
|
||||
CreateDB create = new CreateDB(FileSelectActivity.this,
|
||||
databaseFilename, assignPasswordOnFinish, true);
|
||||
// Create the new database
|
||||
CreateDB create = new CreateDB(FileSelectActivity.this,
|
||||
databaseFilename, assignPasswordOnFinish, true);
|
||||
|
||||
ProgressTask createTask = new ProgressTask(
|
||||
FileSelectActivity.this, create,
|
||||
R.string.progress_create);
|
||||
createTask.run();
|
||||
assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
ProgressTask createTask = new ProgressTask(
|
||||
FileSelectActivity.this, create,
|
||||
R.string.progress_create);
|
||||
createTask.run();
|
||||
assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
} catch (Exception e) {
|
||||
String error = "Unable to create database with this password and key file";
|
||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, error + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -446,33 +434,31 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
@Override
|
||||
public void onFileItemOpenListener(int itemPosition) {
|
||||
new OpenFileHistoryAsyncTask(new OpenFileHistoryAsyncTask.AfterOpenFileHistoryListener() {
|
||||
@Override
|
||||
public void afterOpenFile(String fileName, String keyFile) {
|
||||
try {
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
fileName, keyFile, assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
|
||||
new OpenFileHistoryAsyncTask((fileName, keyFile) -> {
|
||||
// TODO ENCAPSULATE
|
||||
try {
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
fileName, keyFile, assistStructure);
|
||||
}
|
||||
} catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found_content, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} catch (FileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
updateTitleFileListView();
|
||||
}
|
||||
}, fileHistory).execute(itemPosition);
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
|
||||
}
|
||||
} catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found_content, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} catch (FileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
updateTitleFileListView();
|
||||
}, fileHistory).execute(itemPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -486,13 +472,10 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
@Override
|
||||
public boolean onFileSelectClearListener(final FileSelectBean fileSelectBean) {
|
||||
new DeleteFileHistoryAsyncTask(new DeleteFileHistoryAsyncTask.AfterDeleteFileHistoryListener() {
|
||||
@Override
|
||||
public void afterDeleteFile() {
|
||||
fileHistory.deleteFile(fileSelectBean.getFileUri());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateTitleFileListView();
|
||||
}
|
||||
new DeleteFileHistoryAsyncTask(() -> {
|
||||
fileHistory.deleteFile(fileSelectBean.getFileUri());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateTitleFileListView();
|
||||
}, fileHistory, mAdapter).execute(fileSelectBean);
|
||||
return true;
|
||||
}
|
||||
@@ -506,33 +489,23 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
|
||||
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||
new KeyFileHelper.KeyFileCallback() {
|
||||
@Override
|
||||
public void onKeyFileResultCallback(Uri uri) {
|
||||
if (uri != null) {
|
||||
String filename = uri.toString();
|
||||
openFileNameView.setText(filename);
|
||||
}
|
||||
}
|
||||
});
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
if (PreferencesUtil.autoOpenSelectedFile(FileSelectActivity.this)) {
|
||||
launchPasswordActivityWithPath(uri.toString());
|
||||
} else {
|
||||
openFileNameView.setText(uri.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showRationaleForExternalStorage(final PermissionRequest request) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_write_database)
|
||||
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.proceed();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.cancel();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> request.cancel())
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
@@ -48,6 +49,8 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
||||
private FileSelectClearListener fileSelectClearListener;
|
||||
private FileInformationShowListener fileInformationShowListener;
|
||||
|
||||
private @ColorInt
|
||||
int defaultColor;
|
||||
private @ColorInt
|
||||
int warningColor;
|
||||
|
||||
@@ -60,16 +63,19 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true);
|
||||
warningColor = typedValue.data;
|
||||
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
|
||||
defaultColor = typedValue.data;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FileSelectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
public FileSelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = inflater.inflate(R.layout.file_row, parent, false);
|
||||
return new FileSelectViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(FileSelectViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull FileSelectViewHolder holder, int position) {
|
||||
FileSelectBean fileSelectBean = new FileSelectBean(context, listFiles.get(position));
|
||||
// Context menu creation
|
||||
holder.fileContainer.setOnCreateContextMenuListener(new ContextMenuBuilder(fileSelectBean));
|
||||
@@ -87,6 +93,10 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
||||
holder.fileInformation.setColorFilter(
|
||||
warningColor,
|
||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
holder.fileInformation.setColorFilter(
|
||||
defaultColor,
|
||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
// Click on information
|
||||
if (fileInformationShowListener != null)
|
||||
@@ -154,7 +164,7 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
||||
|
||||
private FileSelectBean fileSelectBean;
|
||||
|
||||
public ContextMenuBuilder(FileSelectBean fileSelectBean) {
|
||||
ContextMenuBuilder(FileSelectBean fileSelectBean) {
|
||||
this.fileSelectBean = fileSelectBean;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,16 @@ public class FileSelectBean implements Serializable {
|
||||
|
||||
private static final String EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents";
|
||||
|
||||
private String fileName = "";
|
||||
private String fileName;
|
||||
private Uri fileUri;
|
||||
private Date lastModification = new Date();
|
||||
private long size = 0;
|
||||
private Date lastModification;
|
||||
private long size;
|
||||
|
||||
public FileSelectBean(Context context, String pathFile) {
|
||||
fileName = "";
|
||||
lastModification = new Date();
|
||||
size = 0;
|
||||
|
||||
fileUri = Uri.parse(pathFile);
|
||||
if (EXTERNAL_STORAGE_AUTHORITY.equals(fileUri.getAuthority())) {
|
||||
DocumentFile file = DocumentFile.fromSingleUri(context, fileUri);
|
||||
@@ -49,6 +53,10 @@ public class FileSelectBean implements Serializable {
|
||||
fileName = file.getName();
|
||||
lastModification = new Date(file.lastModified());
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
fileName = fileUri.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean notFound() {
|
||||
|
||||
@@ -17,29 +17,35 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.keepassdroid.compat.ContentResolverCompat;
|
||||
import com.keepassdroid.compat.StorageAF;
|
||||
import com.keepassdroid.fileselect.BrowserDialog;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
import com.keepassdroid.utils.Interaction;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class KeyFileHelper {
|
||||
|
||||
public static final int GET_CONTENT = 25745;
|
||||
private static final String TAG = "KeyFileHelper";
|
||||
|
||||
public static final String OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE";
|
||||
|
||||
private static final int GET_CONTENT = 25745;
|
||||
private static final int OPEN_DOC = 25845;
|
||||
private static final int FILE_BROWSE = 25645;
|
||||
|
||||
@@ -66,34 +72,53 @@ public class KeyFileHelper {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (StorageAF.useStorageFramework(activity)) {
|
||||
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION|
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
if(fragment != null)
|
||||
fragment.startActivityForResult(i, OPEN_DOC);
|
||||
else
|
||||
activity.startActivityForResult(i, OPEN_DOC);
|
||||
} else {
|
||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
|
||||
try {
|
||||
if(fragment != null)
|
||||
fragment.startActivityForResult(i, GET_CONTENT);
|
||||
else
|
||||
activity.startActivityForResult(i, GET_CONTENT);
|
||||
} catch (ActivityNotFoundException|SecurityException e) {
|
||||
lookForOpenIntentsFilePicker(dataUri.onRequestIntentFilePicker());
|
||||
try {
|
||||
if (StorageAF.useStorageFramework(activity)) {
|
||||
openActivityWithActionOpenDocument();
|
||||
} else {
|
||||
openActivityWithActionGetContent();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG,"Enable to start the file picker activity", e);
|
||||
|
||||
// Open File picker if can't open activity
|
||||
Uri uri = null;
|
||||
if (dataUri != null)
|
||||
uri = dataUri.onRequestIntentFilePicker();
|
||||
if(lookForOpenIntentsFilePicker(uri))
|
||||
showBrowserDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openActivityWithActionOpenDocument() {
|
||||
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
} else {
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
if (fragment != null)
|
||||
fragment.startActivityForResult(i, OPEN_DOC);
|
||||
else
|
||||
activity.startActivityForResult(i, OPEN_DOC);
|
||||
}
|
||||
|
||||
private void openActivityWithActionGetContent() {
|
||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
if(fragment != null)
|
||||
fragment.startActivityForResult(i, GET_CONTENT);
|
||||
else
|
||||
activity.startActivityForResult(i, GET_CONTENT);
|
||||
}
|
||||
|
||||
public OpenFileOnClickViewListener getOpenFileOnClickViewListener() {
|
||||
return new OpenFileOnClickViewListener(null);
|
||||
}
|
||||
@@ -102,49 +127,46 @@ public class KeyFileHelper {
|
||||
return new OpenFileOnClickViewListener(dataUri);
|
||||
}
|
||||
|
||||
private void lookForOpenIntentsFilePicker(Uri dataUri) {
|
||||
if (Interaction.isIntentAvailable(activity, Intents.OPEN_INTENTS_FILE_BROWSE)) {
|
||||
Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE);
|
||||
|
||||
// Get file path parent if possible
|
||||
try {
|
||||
private boolean lookForOpenIntentsFilePicker(@Nullable Uri dataUri) {
|
||||
boolean showBrowser = false;
|
||||
try {
|
||||
if (Interaction.isIntentAvailable(activity, OPEN_INTENTS_FILE_BROWSE)) {
|
||||
Intent i = new Intent(OPEN_INTENTS_FILE_BROWSE);
|
||||
// Get file path parent if possible
|
||||
if (dataUri != null
|
||||
&& dataUri.toString().length() > 0
|
||||
&& dataUri.getScheme().equals("file")) {
|
||||
i.setData(dataUri);
|
||||
//i.setData(Uri.parse("file://" + mDbUri.getPath()));
|
||||
/*
|
||||
TODO Verify Intent File Picker
|
||||
File keyfile = new File(mDbUri.getPath());
|
||||
File parent = keyfile.getParentFile();
|
||||
if (parent != null) {
|
||||
i.setData(Uri.parse("file://" + parent.getAbsolutePath()));
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
Log.w(getClass().getName(), "Unable to read the URI");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
Log.w(getClass().getName(), "Unable to read the URI " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
if(fragment != null)
|
||||
fragment.startActivityForResult(i, FILE_BROWSE);
|
||||
else
|
||||
activity.startActivityForResult(i, FILE_BROWSE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
showBrowserDialog();
|
||||
} else {
|
||||
showBrowser = true;
|
||||
}
|
||||
} else {
|
||||
showBrowserDialog();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Enable to start OPEN_INTENTS_FILE_BROWSE", e);
|
||||
showBrowser = true;
|
||||
}
|
||||
return showBrowser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Browser dialog to select file picker app
|
||||
*/
|
||||
private void showBrowserDialog() {
|
||||
BrowserDialog browserDialog = new BrowserDialog(activity);
|
||||
browserDialog.show();
|
||||
try {
|
||||
BrowserDialog browserDialog = new BrowserDialog();
|
||||
if (fragment != null && fragment.getFragmentManager() != null)
|
||||
browserDialog.show(fragment.getFragmentManager(), "browserDialog");
|
||||
else if (activity.getFragmentManager() != null)
|
||||
browserDialog.show(((FragmentActivity) activity).getSupportFragmentManager(), "browserDialog");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Can't open BrowserDialog", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
package com.keepassdroid.fingerprint;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
@@ -30,6 +30,7 @@ import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v4.os.CancellationSignal;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
@@ -47,7 +48,9 @@ import javax.crypto.spec.IvParameterSpec;
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerPrintHelper {
|
||||
|
||||
private static final String FINGERPRINT_KEYSTORE_KEY = "example-key";
|
||||
private static final String TAG = FingerPrintHelper.class.getName();
|
||||
|
||||
private static final String FINGERPRINT_KEYSTORE_KEY = "com.kunzisoft.keepass.fingerprint.key";
|
||||
|
||||
private FingerprintManagerCompat fingerprintManager;
|
||||
private KeyStore keyStore = null;
|
||||
@@ -65,8 +68,7 @@ public class FingerPrintHelper {
|
||||
this.authenticationCallback = authenticationCallback;
|
||||
}
|
||||
|
||||
public void startListening() {
|
||||
|
||||
public synchronized void startListening() {
|
||||
// starts listening for fingerprints with the initialised crypto object
|
||||
cancellationSignal = new CancellationSignal();
|
||||
fingerprintManager.authenticate(
|
||||
@@ -77,7 +79,7 @@ public class FingerPrintHelper {
|
||||
null);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
public synchronized void stopListening() {
|
||||
if (!isFingerprintInitialized(false)) {
|
||||
return;
|
||||
}
|
||||
@@ -113,6 +115,7 @@ public class FingerPrintHelper {
|
||||
this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
|
||||
setInitOk(true);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to initialize the keystore", e);
|
||||
setInitOk(false);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
@@ -142,6 +145,8 @@ public class FingerPrintHelper {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stopListening();
|
||||
|
||||
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
|
||||
keyStore.load(null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
|
||||
@@ -149,10 +154,13 @@ public class FingerPrintHelper {
|
||||
|
||||
startListening();
|
||||
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException);
|
||||
deleteEntryKey();
|
||||
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException);
|
||||
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
@@ -164,14 +172,15 @@ public class FingerPrintHelper {
|
||||
try {
|
||||
// actual do encryption here
|
||||
byte[] encrypted = cipher.doFinal(value.getBytes());
|
||||
final String encryptedValue = Base64.encodeToString(encrypted, Base64.DEFAULT);
|
||||
final String encryptedValue = Base64.encodeToString(encrypted, Base64.NO_WRAP);
|
||||
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
|
||||
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.DEFAULT);
|
||||
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.NO_WRAP);
|
||||
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
|
||||
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to encrypt data", e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
@@ -181,21 +190,26 @@ public class FingerPrintHelper {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stopListening();
|
||||
|
||||
createNewKeyIfNeeded(false);
|
||||
keyStore.load(null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
|
||||
|
||||
// important to restore spec here that was used for decryption
|
||||
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
|
||||
final byte[] iv = Base64.decode(ivSpecValue, Base64.NO_WRAP);
|
||||
final IvParameterSpec spec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
|
||||
startListening();
|
||||
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
|
||||
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException);
|
||||
deleteEntryKey();
|
||||
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException);
|
||||
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
@@ -206,15 +220,17 @@ public class FingerPrintHelper {
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
final byte[] encrypted = Base64.decode(encryptedValue, Base64.DEFAULT);
|
||||
final byte[] encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP);
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
final String decryptedString = new String(decrypted);
|
||||
|
||||
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||
fingerPrintCallback.handleDecryptedResult(decryptedString);
|
||||
} catch (final BadPaddingException badPaddingException) {
|
||||
Log.e(TAG, "Unable to decrypt data", badPaddingException);
|
||||
fingerPrintCallback.onInvalidKeyException(badPaddingException);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to decrypt data", e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
@@ -249,6 +265,7 @@ public class FingerPrintHelper {
|
||||
keyGenerator.generateKey();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
@@ -262,6 +279,7 @@ public class FingerPrintHelper {
|
||||
| NoSuchAlgorithmException
|
||||
| IOException
|
||||
| NullPointerException e) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e);
|
||||
if (fingerPrintCallback != null)
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class UnavailableFeatureDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String MIN_REQUIRED_VERSION_ARG = "MIN_REQUIRED_VERSION_ARG";
|
||||
private int minVersionRequired = Build.VERSION_CODES.M;
|
||||
|
||||
public static UnavailableFeatureDialogFragment getInstance(int minVersionRequired) {
|
||||
UnavailableFeatureDialogFragment fragment = new UnavailableFeatureDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(MIN_REQUIRED_VERSION_ARG, minVersionRequired);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
if (getArguments() != null && getArguments().containsKey(MIN_REQUIRED_VERSION_ARG))
|
||||
minVersionRequired = getArguments().getInt(MIN_REQUIRED_VERSION_ARG);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
String message = getString(R.string.unavailable_feature_text).concat("\n");
|
||||
if(Build.VERSION.SDK_INT < minVersionRequired)
|
||||
message = message.concat(getString(R.string.unavailable_feature_version,
|
||||
Build.VERSION.SDK_INT,
|
||||
minVersionRequired));
|
||||
else
|
||||
message = message.concat(getString(R.string.unavailable_feature_hardware));
|
||||
|
||||
builder.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) { }
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
229
app/src/main/java/com/keepassdroid/notifications/NotificationCopyingService.java
Executable file
229
app/src/main/java/com/keepassdroid/notifications/NotificationCopyingService.java
Executable file
@@ -0,0 +1,229 @@
|
||||
package com.keepassdroid.notifications;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepassdroid.database.exception.SamsungClipboardException;
|
||||
import com.keepassdroid.timeout.ClipboardHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class NotificationCopyingService extends Service {
|
||||
|
||||
private static final String TAG = NotificationCopyingService.class.getName();
|
||||
private static final String CHANNEL_ID_COPYING = "CHANNEL_ID_COPYING";
|
||||
private static final String CHANNEL_NAME_COPYING = "Copy fields";
|
||||
|
||||
public static final String ACTION_NEW_NOTIFICATION = "ACTION_NEW_NOTIFICATION";
|
||||
public static final String EXTRA_ENTRY_TITLE = "EXTRA_ENTRY_TITLE";
|
||||
public static final String EXTRA_FIELDS = "EXTRA_FIELDS";
|
||||
public static final String ACTION_CLEAN_CLIPBOARD = "ACTION_CLEAN_CLIPBOARD";
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private ClipboardHelper clipboardHelper;
|
||||
private Thread cleanNotificationTimer;
|
||||
private Thread countingDownTask;
|
||||
private int notificationId = 1;
|
||||
private long notificationTimeoutMilliSecs;
|
||||
|
||||
public NotificationCopyingService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
clipboardHelper = new ClipboardHelper(this);
|
||||
|
||||
// Create notification channel for Oreo+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_COPYING,
|
||||
CHANNEL_NAME_COPYING,
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
//Get settings
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String sClipClear = prefs.getString(getString(R.string.clipboard_timeout_key),
|
||||
getString(R.string.clipboard_timeout_default));
|
||||
notificationTimeoutMilliSecs = Long.parseLong(sClipClear);
|
||||
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "null intent");
|
||||
|
||||
} else if (ACTION_NEW_NOTIFICATION.equals(intent.getAction())) {
|
||||
String title = intent.getStringExtra(EXTRA_ENTRY_TITLE);
|
||||
newNotification(title, constructListOfField(intent));
|
||||
|
||||
} else if (ACTION_CLEAN_CLIPBOARD.equals(intent.getAction())) {
|
||||
stopTask(countingDownTask);
|
||||
try {
|
||||
clipboardHelper.cleanClipboard();
|
||||
} catch (SamsungClipboardException e) {
|
||||
Log.e(TAG, "Clipboard can't be cleaned", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (String actionKey : NotificationField.getAllActionKeys()) {
|
||||
if (actionKey.equals(intent.getAction())) {
|
||||
NotificationField fieldToCopy = intent.getParcelableExtra(
|
||||
NotificationField.getExtraKeyLinkToActionKey(actionKey));
|
||||
ArrayList<NotificationField> nextFields = constructListOfField(intent);
|
||||
// Remove the current field from the next fields
|
||||
if (nextFields.contains(fieldToCopy))
|
||||
nextFields.remove(fieldToCopy);
|
||||
copyField(fieldToCopy, nextFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private ArrayList<NotificationField> constructListOfField(Intent intent) {
|
||||
ArrayList<NotificationField> fieldList = new ArrayList<>();
|
||||
if (intent != null && intent.getExtras() != null) {
|
||||
if (intent.getExtras().containsKey(EXTRA_FIELDS))
|
||||
fieldList = intent.getParcelableArrayListExtra(EXTRA_FIELDS);
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
private PendingIntent getCopyPendingIntent(NotificationField fieldToCopy, ArrayList<NotificationField> fieldsToAdd) {
|
||||
Intent copyIntent = new Intent(this, NotificationCopyingService.class);
|
||||
copyIntent.setAction(fieldToCopy.getActionKey());
|
||||
copyIntent.putExtra(fieldToCopy.getExtraKey(), fieldToCopy);
|
||||
copyIntent.putParcelableArrayListExtra(EXTRA_FIELDS, fieldsToAdd);
|
||||
|
||||
return PendingIntent.getService(
|
||||
this, 0, copyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private void newNotification(@Nullable String title, ArrayList<NotificationField> fieldsToAdd) {
|
||||
stopTask(countingDownTask);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_COPYING)
|
||||
.setSmallIcon(R.drawable.ic_key_white_24dp);
|
||||
if (title != null)
|
||||
builder.setContentTitle(title);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
builder.setVisibility(Notification.VISIBILITY_SECRET);
|
||||
|
||||
if (fieldsToAdd.size() > 0) {
|
||||
NotificationField field = fieldsToAdd.get(0);
|
||||
builder.setContentText(field.copyText);
|
||||
builder.setContentIntent(getCopyPendingIntent(field, fieldsToAdd));
|
||||
|
||||
// Add extra actions without 1st field
|
||||
List<NotificationField> fieldsWithoutFirstField = new ArrayList<>(fieldsToAdd);
|
||||
fieldsWithoutFirstField.remove(field);
|
||||
// Add extra actions
|
||||
for (NotificationField fieldToAdd : fieldsWithoutFirstField) {
|
||||
builder.addAction(R.drawable.ic_key_white_24dp, fieldToAdd.label,
|
||||
getCopyPendingIntent(fieldToAdd, fieldsToAdd));
|
||||
}
|
||||
}
|
||||
|
||||
notificationManager.cancel(notificationId);
|
||||
notificationManager.notify(++notificationId, builder.build());
|
||||
|
||||
int myNotificationId = notificationId;
|
||||
stopTask(cleanNotificationTimer);
|
||||
cleanNotificationTimer = new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(notificationTimeoutMilliSecs);
|
||||
} catch (InterruptedException e) {
|
||||
cleanNotificationTimer = null;
|
||||
return;
|
||||
}
|
||||
notificationManager.cancel(myNotificationId);
|
||||
});
|
||||
cleanNotificationTimer.start();
|
||||
}
|
||||
|
||||
private void copyField(NotificationField fieldToCopy, ArrayList<NotificationField> nextFields) {
|
||||
stopTask(countingDownTask);
|
||||
stopTask(cleanNotificationTimer);
|
||||
|
||||
try {
|
||||
clipboardHelper.copyToClipboard(fieldToCopy.label, fieldToCopy.value);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_COPYING)
|
||||
.setSmallIcon(R.drawable.ic_key_white_24dp)
|
||||
.setContentTitle(fieldToCopy.label);
|
||||
|
||||
// New action with next field if click
|
||||
if (nextFields.size() > 0) {
|
||||
NotificationField nextField = nextFields.get(0);
|
||||
builder.setContentText(nextField.copyText);
|
||||
builder.setContentIntent(getCopyPendingIntent(nextField, nextFields));
|
||||
// Else tell to swipe for a clean
|
||||
} else {
|
||||
builder.setContentText(getString(R.string.clipboard_swipe_clean));
|
||||
}
|
||||
|
||||
Intent cleanIntent = new Intent(this, NotificationCopyingService.class);
|
||||
cleanIntent.setAction(ACTION_CLEAN_CLIPBOARD);
|
||||
PendingIntent cleanPendingIntent = PendingIntent.getService(
|
||||
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setDeleteIntent(cleanPendingIntent);
|
||||
|
||||
int myNotificationId = notificationId;
|
||||
|
||||
countingDownTask = new Thread(() -> {
|
||||
int maxPos = 100;
|
||||
long posDurationMills = notificationTimeoutMilliSecs / maxPos;
|
||||
for (int pos = maxPos; pos > 0; --pos) {
|
||||
builder.setProgress(maxPos, pos, false);
|
||||
notificationManager.notify(myNotificationId, builder.build());
|
||||
try {
|
||||
Thread.sleep(posDurationMills);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
countingDownTask = null;
|
||||
notificationManager.cancel(myNotificationId);
|
||||
// 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();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Clipboard can't be populate", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTask(Thread task) {
|
||||
if (task != null && task.isAlive())
|
||||
task.interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2017 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.keepassdroid.notifications;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class to manage fields in Notifications
|
||||
*/
|
||||
public class NotificationField implements Parcelable {
|
||||
|
||||
private static final String TAG = NotificationField.class.getName();
|
||||
|
||||
private NotificationFieldId id;
|
||||
String value;
|
||||
String label;
|
||||
String copyText;
|
||||
|
||||
public NotificationField(NotificationFieldId id, String value, Resources resources) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
this.label = getLabel(resources);
|
||||
this.copyText = getCopyText(resources);
|
||||
}
|
||||
|
||||
public NotificationField(NotificationFieldId id, String value, String label, Resources resources) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.copyText = getCopyText(resources);
|
||||
}
|
||||
|
||||
protected NotificationField(Parcel in) {
|
||||
id = NotificationFieldId.values()[in.readInt()];
|
||||
value = in.readString();
|
||||
label = in.readString();
|
||||
copyText = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(id.ordinal());
|
||||
dest.writeString(value);
|
||||
dest.writeString(label);
|
||||
dest.writeString(copyText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<NotificationField> CREATOR = new Creator<NotificationField>() {
|
||||
@Override
|
||||
public NotificationField createFromParcel(Parcel in) {
|
||||
return new NotificationField(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationField[] newArray(int size) {
|
||||
return new NotificationField[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
NotificationField field = (NotificationField) o;
|
||||
return id.equals(field.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
public enum NotificationFieldId {
|
||||
USERNAME, PASSWORD, FIELD_A, FIELD_B, FIELD_C;
|
||||
|
||||
public static NotificationFieldId[] getAnonymousFieldId() {
|
||||
return new NotificationFieldId[] {FIELD_A, FIELD_B, FIELD_C};
|
||||
}
|
||||
}
|
||||
|
||||
private static final String ACTION_COPY_PREFIX = "ACTION_COPY_";
|
||||
private static final String EXTRA_KEY_PREFIX = "EXTRA_";
|
||||
|
||||
/**
|
||||
* Return EXTRA_KEY link to ACTION_KEY, or null if ACTION_KEY is unknown
|
||||
*/
|
||||
public static @Nullable String getExtraKeyLinkToActionKey(String actionKey) {
|
||||
try {
|
||||
if (actionKey.startsWith(ACTION_COPY_PREFIX)) {
|
||||
String idName = actionKey.substring(ACTION_COPY_PREFIX.length(), actionKey.length());
|
||||
return getExtraKey(NotificationFieldId.valueOf(idName));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Can't get Extra Key from Action Key", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getActionKey(NotificationFieldId id) {
|
||||
return ACTION_COPY_PREFIX + id.name();
|
||||
}
|
||||
|
||||
public String getActionKey() {
|
||||
return getActionKey(id);
|
||||
}
|
||||
|
||||
private static String getExtraKey(NotificationFieldId id) {
|
||||
return EXTRA_KEY_PREFIX + id.name();
|
||||
}
|
||||
|
||||
public String getExtraKey() {
|
||||
return getExtraKey(id);
|
||||
}
|
||||
|
||||
public static List<String> getAllActionKeys() {
|
||||
List<String> actionKeys = new ArrayList<>();
|
||||
for (NotificationFieldId id : NotificationFieldId.values()) {
|
||||
actionKeys.add(getActionKey(id));
|
||||
}
|
||||
return actionKeys;
|
||||
}
|
||||
|
||||
private String getLabel(Resources resources) {
|
||||
switch (id) {
|
||||
case USERNAME:
|
||||
return resources.getString(R.string.entry_user_name);
|
||||
case PASSWORD:
|
||||
return resources.getString(R.string.entry_password);
|
||||
default:
|
||||
return id.name();
|
||||
}
|
||||
}
|
||||
|
||||
private String getCopyText(Resources resources) {
|
||||
return resources.getString(R.string.select_to_copy, label);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.keepassdroid.view;
|
||||
package com.keepassdroid.password;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
@@ -22,8 +22,6 @@ package com.keepassdroid.password;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
@@ -47,11 +45,11 @@ import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.activities.GroupActivity;
|
||||
import com.keepassdroid.activities.LockingActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.compat.BackupManagerCompat;
|
||||
@@ -60,7 +58,7 @@ import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.edit.LoadDB;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.dialogs.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.fingerprint.FingerPrintAnimatedVector;
|
||||
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
@@ -69,8 +67,8 @@ import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.view.FingerPrintDialog;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.keepassdroid.fingerprint.FingerPrintDialog;
|
||||
import com.keepassdroid.fileselect.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
@@ -91,8 +89,9 @@ import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.STORE_MODE;
|
||||
public class PasswordActivity extends StylishActivity
|
||||
implements FingerPrintHelper.FingerPrintCallback, UriIntentInitTaskCallback {
|
||||
|
||||
private static final String TAG = PasswordActivity.class.getName();
|
||||
|
||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||
public static final int RESULT_EXIT_LOCK = 1450;
|
||||
|
||||
private static final String KEY_PASSWORD = "password";
|
||||
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
|
||||
@@ -204,19 +203,16 @@ public class PasswordActivity extends StylishActivity
|
||||
boolean keyFileResult = false;
|
||||
if (keyFileHelper != null) {
|
||||
keyFileResult = keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||
new KeyFileHelper.KeyFileCallback() {
|
||||
@Override
|
||||
public void onKeyFileResultCallback(Uri uri) {
|
||||
if (uri != null) {
|
||||
populateKeyFileTextView(uri.toString());
|
||||
}
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
populateKeyFileTextView(uri.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!keyFileResult) {
|
||||
// this block if not a key file response
|
||||
switch (resultCode) {
|
||||
case RESULT_EXIT_LOCK:
|
||||
case LockingActivity.RESULT_EXIT_LOCK:
|
||||
case Activity.RESULT_CANCELED:
|
||||
setEmptyViews();
|
||||
App.getDB().clear();
|
||||
@@ -237,20 +233,20 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
setContentView(R.layout.password);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
confirmButtonView = (Button) findViewById(R.id.pass_ok);
|
||||
filenameView = (TextView) findViewById(R.id.filename);
|
||||
passwordView = (EditText) findViewById(R.id.password);
|
||||
keyFileView = (EditText) findViewById(R.id.pass_keyfile);
|
||||
checkboxPasswordView = (CompoundButton) findViewById(R.id.password_checkbox);
|
||||
checkboxKeyfileView = (CompoundButton) findViewById(R.id.keyfile_checkox);
|
||||
checkboxDefaultDatabaseView = (CompoundButton) findViewById(R.id.default_database);
|
||||
confirmButtonView = findViewById(R.id.pass_ok);
|
||||
filenameView = findViewById(R.id.filename);
|
||||
passwordView = findViewById(R.id.password);
|
||||
keyFileView = findViewById(R.id.pass_keyfile);
|
||||
checkboxPasswordView = findViewById(R.id.password_checkbox);
|
||||
checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
|
||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database);
|
||||
|
||||
View browseView = findViewById(R.id.browse_button);
|
||||
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
|
||||
@@ -265,7 +261,7 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (!editable.toString().isEmpty() && !checkboxKeyfileView.isChecked())
|
||||
if (!editable.toString().isEmpty() && !checkboxPasswordView.isChecked())
|
||||
checkboxPasswordView.setChecked(true);
|
||||
}
|
||||
});
|
||||
@@ -288,10 +284,10 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerprintContainerView = findViewById(R.id.fingerprint_container);
|
||||
fingerprintTextView = (TextView) findViewById(R.id.fingerprint_label);
|
||||
fingerprintTextView = findViewById(R.id.fingerprint_label);
|
||||
initForFingerprint();
|
||||
fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this,
|
||||
(ImageView) findViewById(R.id.fingerprint_image));
|
||||
findViewById(R.id.fingerprint_image));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -308,6 +304,11 @@ public class PasswordActivity extends StylishActivity
|
||||
setEmptyViews();
|
||||
}
|
||||
|
||||
// Show message if exists
|
||||
CharSequence appMessage = App.getMessage();
|
||||
if (! appMessage.toString().isEmpty())
|
||||
Toast.makeText(this, appMessage, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
@@ -441,7 +442,7 @@ public class PasswordActivity extends StylishActivity
|
||||
if ( !fingerprintMustBeConfigured ) {
|
||||
final boolean validInput = s.length() > 0;
|
||||
// encrypt or decrypt mode based on how much input or not
|
||||
setFingerPrintTextView(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||
setFingerPrintView(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||
if (validInput)
|
||||
toggleFingerprintMode(STORE_MODE);
|
||||
else
|
||||
@@ -456,22 +457,30 @@ public class PasswordActivity extends StylishActivity
|
||||
public void onAuthenticationError(
|
||||
final int errorCode,
|
||||
final CharSequence errString) {
|
||||
Log.i(getClass().getName(), errString.toString());
|
||||
switch (errorCode) {
|
||||
case 5:
|
||||
Log.i(TAG, "Fingerprint authentication error. Code : " + errorCode + " Error : " + errString);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Fingerprint authentication error. Code : " + errorCode + " Error : " + errString);
|
||||
setFingerPrintView(errString.toString(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(
|
||||
final int helpCode,
|
||||
final CharSequence helpString) {
|
||||
Log.w(TAG, "Fingerprint authentication help. Code : " + helpCode + " Help : " + helpString);
|
||||
showError(helpString);
|
||||
reInitWithSameFingerprintMode();
|
||||
setFingerPrintView(helpString.toString(), true);
|
||||
fingerprintTextView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
Log.e(TAG, "Fingerprint authentication failed, fingerprint not recognized");
|
||||
showError(R.string.fingerprint_not_recognized);
|
||||
reInitWithSameFingerprintMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -505,7 +514,7 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initEncryptData() {
|
||||
setFingerPrintTextView(R.string.store_with_fingerprint);
|
||||
setFingerPrintView(R.string.store_with_fingerprint);
|
||||
fingerPrintMode = STORE_MODE;
|
||||
if (fingerPrintHelper != null)
|
||||
fingerPrintHelper.initEncryptData();
|
||||
@@ -513,7 +522,7 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initDecryptData() {
|
||||
setFingerPrintTextView(R.string.scanning_fingerprint);
|
||||
setFingerPrintView(R.string.scanning_fingerprint);
|
||||
fingerPrintMode = OPEN_MODE;
|
||||
if (fingerPrintHelper != null) {
|
||||
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
|
||||
@@ -526,12 +535,12 @@ public class PasswordActivity extends StylishActivity
|
||||
private synchronized void toggleFingerprintMode(final FingerPrintHelper.Mode newMode) {
|
||||
if( !newMode.equals(fingerPrintMode) ) {
|
||||
fingerPrintMode = newMode;
|
||||
reInitWithSameFingerprintMode();
|
||||
reInitWithFingerprintMode();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private synchronized void reInitWithSameFingerprintMode() {
|
||||
private synchronized void reInitWithFingerprintMode() {
|
||||
switch (fingerPrintMode) {
|
||||
case STORE_MODE:
|
||||
initEncryptData();
|
||||
@@ -546,8 +555,6 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (fingerPrintAnimatedVector != null) {
|
||||
fingerPrintAnimatedVector.stopScan();
|
||||
@@ -558,32 +565,32 @@ public class PasswordActivity extends StylishActivity
|
||||
fingerPrintHelper.stopListening();
|
||||
}
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private void setFingerPrintVisibility(final int vis) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintContainerView.setVisibility(vis);
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> fingerprintContainerView.setVisibility(vis));
|
||||
}
|
||||
|
||||
private void setFingerPrintTextView(final int textId) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintTextView.setText(textId);
|
||||
}
|
||||
});
|
||||
private void setFingerPrintView(final int textId) {
|
||||
setFingerPrintView(textId, false);
|
||||
}
|
||||
|
||||
private void setFingerPrintAlphaImageView(final float alpha) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintContainerView.setAlpha(alpha);
|
||||
}
|
||||
private void setFingerPrintView(final CharSequence text) {
|
||||
setFingerPrintView(text, false);
|
||||
}
|
||||
|
||||
private void setFingerPrintView(final int textId, boolean lock) {
|
||||
setFingerPrintView(getString(textId), lock);
|
||||
}
|
||||
|
||||
private void setFingerPrintView(final CharSequence text, boolean lock) {
|
||||
runOnUiThread(() -> {
|
||||
if (lock) {
|
||||
fingerprintContainerView.setAlpha(0.6f);
|
||||
} else
|
||||
fingerprintContainerView.setAlpha(1f);
|
||||
fingerprintTextView.setText(text);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -598,28 +605,23 @@ public class PasswordActivity extends StylishActivity
|
||||
// fingerprint is available but not configured show icon but in disabled state with some information
|
||||
else {
|
||||
// show explanations
|
||||
fingerprintContainerView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
|
||||
fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
|
||||
}
|
||||
fingerprintContainerView.setOnClickListener(view -> {
|
||||
FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
|
||||
fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
|
||||
});
|
||||
setFingerPrintVisibility(View.VISIBLE);
|
||||
|
||||
if (!fingerPrintHelper.hasEnrolledFingerprints()) {
|
||||
setFingerPrintAlphaImageView(0.6f);
|
||||
// This happens when no fingerprints are registered. Listening won't start
|
||||
setFingerPrintTextView(R.string.configure_fingerprint);
|
||||
setFingerPrintView(R.string.configure_fingerprint, true);
|
||||
}
|
||||
// finally fingerprint available and configured so we can use it
|
||||
else {
|
||||
fingerprintMustBeConfigured = false;
|
||||
setFingerPrintAlphaImageView(1f);
|
||||
|
||||
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||
if (!prefsNoBackup.contains(getPreferenceKeyValue())) {
|
||||
setFingerPrintTextView(R.string.no_password_stored);
|
||||
setFingerPrintView(R.string.no_password_stored);
|
||||
// listen for encryption
|
||||
initEncryptData();
|
||||
}
|
||||
@@ -635,7 +637,7 @@ public class PasswordActivity extends StylishActivity
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void removePrefsNoBackupKeys() {
|
||||
private void removePrefsNoBackupKey() {
|
||||
prefsNoBackup.edit()
|
||||
.remove(getPreferenceKeyValue())
|
||||
.remove(getPreferenceKeyIvSpec())
|
||||
@@ -651,7 +653,7 @@ public class PasswordActivity extends StylishActivity
|
||||
.putString(getPreferenceKeyIvSpec(), ivSpec)
|
||||
.apply();
|
||||
verifyAllViewsAndLoadDatabase();
|
||||
setFingerPrintTextView(R.string.encrypted_value_stored);
|
||||
setFingerPrintView(R.string.encrypted_value_stored);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -663,36 +665,31 @@ public class PasswordActivity extends StylishActivity
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {
|
||||
showError(R.string.fingerprint_invalid_key);
|
||||
removePrefsNoBackupKeys();
|
||||
e.printStackTrace();
|
||||
reInitWithSameFingerprintMode(); // restarts listening
|
||||
showError(getString(R.string.fingerprint_invalid_key));
|
||||
deleteEntryKey();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
showError(R.string.fingerprint_error);
|
||||
e.printStackTrace();
|
||||
reInitWithSameFingerprintMode();
|
||||
showError(getString(R.string.fingerprint_error, e.getMessage()));
|
||||
setFingerPrintView(e.getLocalizedMessage(), true);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void deleteEntryKey() {
|
||||
fingerPrintHelper.deleteEntryKey();
|
||||
removePrefsNoBackupKey();
|
||||
fingerPrintMode = NOT_CONFIGURED_MODE;
|
||||
checkFingerprintAvailability();
|
||||
}
|
||||
|
||||
private void showError(final int messageId) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
showError(getString(messageId));
|
||||
}
|
||||
|
||||
private void showError(final CharSequence message) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
||||
@@ -782,10 +779,7 @@ public class PasswordActivity extends StylishActivity
|
||||
break;
|
||||
case R.id.menu_fingerprint_remove_key:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintHelper.deleteEntryKey();
|
||||
removePrefsNoBackupKeys();
|
||||
fingerPrintMode = NOT_CONFIGURED_MODE;
|
||||
checkFingerprintAvailability();
|
||||
deleteEntryKey();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -822,25 +816,13 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
// Recheck fingerprint if error
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
//setEmptyViews();
|
||||
//mMessage = getString(R.string.fingerprint_error) + " : " + mMessage;
|
||||
// TODO Change fingerprint message
|
||||
// Stay with the same mode
|
||||
reInitWithSameFingerprintMode();
|
||||
reInitWithFingerprintMode();
|
||||
}
|
||||
|
||||
if (db.passwordEncodingError) {
|
||||
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
|
||||
dialog.show(PasswordActivity.this, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(
|
||||
DialogInterface dialog,
|
||||
int which) {
|
||||
launchGroupActivity();
|
||||
}
|
||||
|
||||
});
|
||||
dialog.show(PasswordActivity.this, (dialog1, which) -> launchGroupActivity());
|
||||
} else if (mSuccess) {
|
||||
launchGroupActivity();
|
||||
} else {
|
||||
@@ -945,18 +927,8 @@ public class PasswordActivity extends StylishActivity
|
||||
void showRationaleForExternalStorage(final PermissionRequest request) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_read_database)
|
||||
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.proceed();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.cancel();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> request.cancel())
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R;
|
||||
|
||||
public class PasswordGenerator {
|
||||
private static final String UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private static final String LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
||||
private static final String LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
||||
private static final String DIGIT_CHARS = "0123456789";
|
||||
private static final String MINUS_CHAR = "-";
|
||||
private static final String UNDERLINE_CHAR = "_";
|
||||
@@ -35,6 +35,19 @@ public class PasswordGenerator {
|
||||
private static final String SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`";
|
||||
private static final String BRACKET_CHARS = "[]{}()<>";
|
||||
|
||||
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
|
||||
private String extendedChars() {
|
||||
StringBuilder charSet = new StringBuilder();
|
||||
// [U+0080, U+009F] are C1 control characters,
|
||||
// U+00A0 is non-breaking space
|
||||
for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch)
|
||||
charSet.append(ch);
|
||||
// U+00AD is soft hyphen (format character)
|
||||
for(char ch = '\u00AE'; ch < '\u00FF'; ++ch)
|
||||
charSet.append(ch);
|
||||
charSet.append('\u00FF');
|
||||
return charSet.toString();
|
||||
}
|
||||
|
||||
private Context cxt;
|
||||
|
||||
@@ -42,22 +55,48 @@ public class PasswordGenerator {
|
||||
this.cxt = cxt;
|
||||
}
|
||||
|
||||
public String generatePassword(int length, boolean upperCase, boolean lowerCase, boolean digits, boolean minus, boolean underline, boolean space, boolean specials, boolean brackets) throws IllegalArgumentException{
|
||||
public String generatePassword(int length,
|
||||
boolean upperCase,
|
||||
boolean lowerCase,
|
||||
boolean digits,
|
||||
boolean minus,
|
||||
boolean underline,
|
||||
boolean space,
|
||||
boolean specials,
|
||||
boolean brackets,
|
||||
boolean extended) throws IllegalArgumentException{
|
||||
// Desired password length is 0 or less
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException(cxt.getString(R.string.error_wrong_length));
|
||||
}
|
||||
|
||||
// No option has been checked
|
||||
if (!upperCase && !lowerCase && !digits && !minus && !underline && !space && !specials && !brackets) {
|
||||
if ( !upperCase
|
||||
&& !lowerCase
|
||||
&& !digits
|
||||
&& !minus
|
||||
&& !underline
|
||||
&& !space
|
||||
&& !specials
|
||||
&& !brackets
|
||||
&& !extended) {
|
||||
throw new IllegalArgumentException(cxt.getString(R.string.error_pass_gen_type));
|
||||
}
|
||||
|
||||
String characterSet = getCharacterSet(upperCase, lowerCase, digits, minus, underline, space, specials, brackets);
|
||||
String characterSet = getCharacterSet(
|
||||
upperCase,
|
||||
lowerCase,
|
||||
digits,
|
||||
minus,
|
||||
underline,
|
||||
space,
|
||||
specials,
|
||||
brackets,
|
||||
extended);
|
||||
|
||||
int size = characterSet.length();
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
SecureRandom random = new SecureRandom(); // use more secure variant of Random!
|
||||
if (size > 0) {
|
||||
@@ -69,8 +108,16 @@ public class PasswordGenerator {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String getCharacterSet(boolean upperCase, boolean lowerCase, boolean digits, boolean minus, boolean underline, boolean space, boolean specials, boolean brackets) {
|
||||
StringBuffer charSet = new StringBuffer();
|
||||
private String getCharacterSet(boolean upperCase,
|
||||
boolean lowerCase,
|
||||
boolean digits,
|
||||
boolean minus,
|
||||
boolean underline,
|
||||
boolean space,
|
||||
boolean specials,
|
||||
boolean brackets,
|
||||
boolean extended) {
|
||||
StringBuilder charSet = new StringBuilder();
|
||||
|
||||
if (upperCase) {
|
||||
charSet.append(UPPERCASE_CHARS);
|
||||
@@ -103,7 +150,11 @@ public class PasswordGenerator {
|
||||
if (brackets) {
|
||||
charSet.append(BRACKET_CHARS);
|
||||
}
|
||||
|
||||
|
||||
if (extended) {
|
||||
charSet.append(extendedChars());
|
||||
}
|
||||
|
||||
return charSet.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ public class SearchDbHelper {
|
||||
Log.d("SearchDbHelper", "Tried to search with unknown db");
|
||||
return null;
|
||||
}
|
||||
group.name = mCtx.getString(R.string.search_results);
|
||||
group.childEntries = new ArrayList<PwEntry>();
|
||||
group.setName(mCtx.getString(R.string.search_results));
|
||||
group.setEntries(new ArrayList<>());
|
||||
|
||||
// Search all entries
|
||||
Locale loc = Locale.getDefault();
|
||||
@@ -83,11 +83,11 @@ public class SearchDbHelper {
|
||||
PwGroup top = worklist.remove();
|
||||
|
||||
if (pm.isGroupSearchable(top, isOmitBackup)) {
|
||||
for (PwEntry entry : top.childEntries) {
|
||||
processEntries(entry, group.childEntries, qStr, loc);
|
||||
for (PwEntry entry : top.getChildEntries()) {
|
||||
processEntries(entry, group.getChildEntries(), qStr, loc);
|
||||
}
|
||||
|
||||
for (PwGroup childGroup : top.childGroups) {
|
||||
for (PwGroup childGroup : top.getChildGroups()) {
|
||||
if (childGroup != null) {
|
||||
worklist.add(childGroup);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.settings;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
@@ -39,7 +40,7 @@ import android.view.autofill.AutofillManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.fragments.UnavailableFeatureDialogFragment;
|
||||
import com.keepassdroid.dialogs.UnavailableFeatureDialogFragment;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwEncryptionAlgorithm;
|
||||
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||
@@ -130,16 +131,13 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
if (!fingerprintSupported) {
|
||||
// False if under Marshmallow
|
||||
fingerprintEnablePreference.setChecked(false);
|
||||
fingerprintEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
assert fragmentManager != null;
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(getFragmentManager(), "unavailableFeatureDialog");
|
||||
return false;
|
||||
}
|
||||
fingerprintEnablePreference.setOnPreferenceClickListener(preference -> {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
assert fragmentManager != null;
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(getFragmentManager(), "unavailableFeatureDialog");
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,45 +145,39 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
if (!fingerprintSupported) {
|
||||
deleteKeysFingerprints.setEnabled(false);
|
||||
} else {
|
||||
deleteKeysFingerprints.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(getResources().getString(R.string.fingerprint_delete_all_warning))
|
||||
.setIcon(getResources().getDrawable(
|
||||
android.R.drawable.ic_dialog_alert))
|
||||
.setPositiveButton(
|
||||
getResources().getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints(
|
||||
getContext(),
|
||||
new FingerPrintHelper.FingerPrintErrorCallback() {
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {
|
||||
}
|
||||
deleteKeysFingerprints.setOnPreferenceClickListener(preference -> {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(getResources().getString(R.string.fingerprint_delete_all_warning))
|
||||
.setIcon(getResources().getDrawable(
|
||||
android.R.drawable.ic_dialog_alert))
|
||||
.setPositiveButton(
|
||||
getResources().getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints(
|
||||
getContext(),
|
||||
new FingerPrintHelper.FingerPrintErrorCallback() {
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {}
|
||||
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
Toast.makeText(getContext(), R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(
|
||||
getResources().getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
}
|
||||
}).show();
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
Toast.makeText(getContext(),
|
||||
getString(R.string.fingerprint_error, e.getLocalizedMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(
|
||||
getResources().getString(android.R.string.no),
|
||||
(dialog, which) -> {
|
||||
}).show();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -204,7 +196,14 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (((SwitchPreference) preference).isChecked()) {
|
||||
startEnableService();
|
||||
try {
|
||||
startEnableService();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
String error = getString(R.string.error_autofill_enable_service);
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
Log.d(getClass().getName(), error, e);
|
||||
Toast.makeText(getContext(), error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
disableService();
|
||||
}
|
||||
@@ -221,11 +220,11 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void startEnableService() {
|
||||
private void startEnableService() throws ActivityNotFoundException{
|
||||
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
|
||||
intent.setData(Uri.parse("package:com.example.android.autofill.service"));
|
||||
Log.d(getClass().getName(), "enableService(): intent="+ intent);
|
||||
Log.d(getClass().getName(), "enableService(): intent=" + intent);
|
||||
startActivityForResult(intent, REQUEST_CODE_AUTOFILL);
|
||||
} else {
|
||||
Log.d(getClass().getName(), "Sample service already enabled.");
|
||||
|
||||
@@ -125,4 +125,16 @@ public class PreferencesUtil {
|
||||
return prefs.getBoolean(ctx.getString(R.string.monospace_font_fields_enable_key),
|
||||
ctx.getResources().getBoolean(R.bool.monospace_font_fields_enable_default));
|
||||
}
|
||||
|
||||
public static boolean autoOpenSelectedFile(Context ctx) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
return prefs.getBoolean(ctx.getString(R.string.auto_open_file_uri_key),
|
||||
ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default));
|
||||
}
|
||||
|
||||
public static boolean allowCopyPassword(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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
*/
|
||||
package com.keepassdroid.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.keepassdroid.activities.LockingActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.BackupManagerCompat;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
@@ -37,15 +39,28 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
||||
|
||||
private Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
super.onResume();
|
||||
public static void launch(Activity activity) {
|
||||
Intent i = new Intent(activity, SettingsActivity.class);
|
||||
activity.startActivity(i);
|
||||
}
|
||||
|
||||
|
||||
public static void launch(Activity activity, boolean checkLock) {
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
if (!checkLock)
|
||||
launch(activity);
|
||||
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
|
||||
launch(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the main fragment to show in first
|
||||
* @return The main fragment
|
||||
*/
|
||||
protected Fragment retrieveMainFragment() {
|
||||
return new MainPreferenceFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -59,7 +74,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.fragment_container, new MainPreferenceFragment())
|
||||
.add(R.id.fragment_container, retrieveMainFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@@ -80,7 +95,6 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
||||
@Override
|
||||
protected void onStop() {
|
||||
backupManager.dataChanged();
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
@@ -16,13 +16,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/package com.keepassdroid.intents;
|
||||
*/
|
||||
package com.keepassdroid.settings;
|
||||
|
||||
public class Intents {
|
||||
public static final String TIMEOUT = "com.keepassdroid.timeout";
|
||||
|
||||
public static final String COPY_USERNAME = "com.keepassdroid.copy_username";
|
||||
public static final String COPY_PASSWORD = "com.keepassdroid.copy_password";
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
public static final String OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE";
|
||||
public class SettingsAutofillActivity extends SettingsActivity {
|
||||
|
||||
@Override
|
||||
protected Fragment retrieveMainFragment() {
|
||||
return NestedSettingsFragment.newInstance(NestedSettingsFragment.NESTED_SCREEN_FORM_FILLING_KEY);
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,9 @@ import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.database.edit.RunnableOnFinish;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
/** Designed to Pop up a progress dialog, run a thread in the background,
|
||||
* run cleanup in the current thread, close the dialog. Without blocking
|
||||
@@ -82,11 +82,11 @@ public class ProgressTask implements Runnable {
|
||||
}
|
||||
|
||||
private class CloseProcessDialog implements Runnable {
|
||||
|
||||
public void run() {
|
||||
mPd.dismiss();
|
||||
if (mPd != null && mPd.isShowing()) {
|
||||
mPd.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
158
app/src/main/java/com/keepassdroid/timeout/ClipboardHelper.java
Normal file
158
app/src/main/java/com/keepassdroid/timeout/ClipboardHelper.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.timeout;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.database.exception.SamsungClipboardException;
|
||||
import com.keepassdroid.tasks.UIToastTask;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ClipboardHelper {
|
||||
|
||||
private Context context;
|
||||
private ClipboardManager clipboardManager;
|
||||
|
||||
private Timer mTimer = new Timer();
|
||||
|
||||
public ClipboardHelper(Context context) {
|
||||
this.context = context;
|
||||
this.clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
}
|
||||
|
||||
public void timeoutCopyToClipboard(String text) {
|
||||
timeoutCopyToClipboard(text, "");
|
||||
}
|
||||
|
||||
public void timeoutCopyToClipboard(String text, String toastString) {
|
||||
if (!toastString.isEmpty())
|
||||
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show();
|
||||
try {
|
||||
copyToClipboard(text);
|
||||
} catch (SamsungClipboardException e) {
|
||||
showSamsungDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String sClipClear = prefs.getString(context.getString(R.string.clipboard_timeout_key),
|
||||
context.getString(R.string.clipboard_timeout_default));
|
||||
|
||||
long clipClearTime = Long.parseLong(sClipClear);
|
||||
|
||||
if ( clipClearTime > 0 ) {
|
||||
mTimer.schedule(new ClearClipboardTask(context, text), clipClearTime);
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence getClipboard(Context context) {
|
||||
if (clipboardManager.hasPrimaryClip()) {
|
||||
ClipData data = clipboardManager.getPrimaryClip();
|
||||
if (data.getItemCount() > 0) {
|
||||
CharSequence text = data.getItemAt(0).coerceToText(context);
|
||||
if (text != null) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void copyToClipboard(String value) throws SamsungClipboardException {
|
||||
copyToClipboard("", value);
|
||||
}
|
||||
|
||||
public void copyToClipboard(String label, String value) throws SamsungClipboardException {
|
||||
try {
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(label, value));
|
||||
} catch (NullPointerException e) {
|
||||
throw new SamsungClipboardException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanClipboard() throws SamsungClipboardException {
|
||||
cleanClipboard("");
|
||||
}
|
||||
|
||||
public void cleanClipboard(String label) throws SamsungClipboardException {
|
||||
copyToClipboard(label,"");
|
||||
}
|
||||
|
||||
|
||||
// Setup to allow the toast to happen in the foreground
|
||||
private final Handler uiThreadCallback = new Handler();
|
||||
|
||||
// Task which clears the clipboard, and sends a toast to the foreground.
|
||||
private class ClearClipboardTask extends TimerTask {
|
||||
|
||||
private final String mClearText;
|
||||
private final Context mCtx;
|
||||
|
||||
ClearClipboardTask(Context ctx, String clearText) {
|
||||
mClearText = clearText;
|
||||
mCtx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String currentClip = getClipboard(mCtx).toString();
|
||||
if ( currentClip.equals(mClearText) ) {
|
||||
try {
|
||||
cleanClipboard();
|
||||
uiThreadCallback.post(new UIToastTask(mCtx, R.string.ClearClipboard));
|
||||
} catch (SamsungClipboardException e) {
|
||||
uiThreadCallback.post(new UIToastTask(mCtx, R.string.clipboard_error_clear));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showSamsungDialog() {
|
||||
String text = context.getString(R.string.clipboard_error).concat(System.getProperty("line.separator"))
|
||||
.concat(context.getString(R.string.clipboard_error_url));
|
||||
SpannableString s = new SpannableString(text);
|
||||
TextView tv = new TextView(context);
|
||||
tv.setText(s);
|
||||
tv.setAutoLinkMask(Activity.RESULT_OK);
|
||||
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
Linkify.addLinks(s, Linkify.WEB_URLS);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.clipboard_error_title)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||
.setView(tv)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.keepassdroid.timers;
|
||||
package com.keepassdroid.timeout;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -9,24 +9,21 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
import com.keepassdroid.services.TimeoutService;
|
||||
|
||||
public class Timeout {
|
||||
|
||||
public static final String TIMEOUT = "com.keepassdroid.timeout";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
||||
private static String TAG = "KeePass Timeout";
|
||||
|
||||
private static PendingIntent buildIntent(Context ctx) {
|
||||
Intent intent = new Intent(Intents.TIMEOUT);
|
||||
PendingIntent sender = PendingIntent.getBroadcast(ctx, REQUEST_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
return sender;
|
||||
Intent intent = new Intent(TIMEOUT);
|
||||
return PendingIntent.getBroadcast(ctx, REQUEST_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static void start(Context ctx) {
|
||||
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
String sTimeout = prefs.getString(ctx.getString(R.string.app_timeout_key), ctx.getString(R.string.clipboard_timeout_default));
|
||||
|
||||
@@ -48,17 +45,20 @@ public class Timeout {
|
||||
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
Log.d(TAG, "Timeout start");
|
||||
am.set(AlarmManager.RTC, triggerTime, buildIntent(ctx));
|
||||
}
|
||||
if (am != null) {
|
||||
am.set(AlarmManager.RTC, triggerTime, buildIntent(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
public static void cancel(Context ctx) {
|
||||
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
Log.d(TAG, "Timeout cancel");
|
||||
am.cancel(buildIntent(ctx));
|
||||
|
||||
ctx.stopService(new Intent(ctx, TimeoutService.class));
|
||||
if (am != null) {
|
||||
am.cancel(buildIntent(ctx));
|
||||
}
|
||||
|
||||
ctx.stopService(new Intent(ctx, TimeoutService.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,17 +23,18 @@ import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.activities.LockingActivity;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.timers.Timeout;
|
||||
|
||||
public class TimeoutHelper {
|
||||
|
||||
|
||||
private static final String TAG = "TimeoutHelper";
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
public static void pause(Activity act) {
|
||||
public static void recordTime(Activity act) {
|
||||
// Record timeout time in case timeout service is killed
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
@@ -46,14 +47,12 @@ public class TimeoutHelper {
|
||||
if ( App.getDB().Loaded() ) {
|
||||
Timeout.start(act);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void resume(Activity act) {
|
||||
public static boolean checkTime(Activity act) {
|
||||
if ( App.getDB().Loaded() ) {
|
||||
Timeout.cancel(act);
|
||||
}
|
||||
|
||||
|
||||
// Check whether the timeout has expired
|
||||
long cur_time = System.currentTimeMillis();
|
||||
@@ -62,10 +61,9 @@ public class TimeoutHelper {
|
||||
long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), -1);
|
||||
// The timeout never started
|
||||
if (timeout_start == -1) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
String sTimeout = prefs.getString(act.getString(R.string.app_timeout_key), act.getString(R.string.clipboard_timeout_default));
|
||||
long timeout;
|
||||
try {
|
||||
@@ -76,20 +74,19 @@ public class TimeoutHelper {
|
||||
|
||||
// We are set to never timeout
|
||||
if (timeout == -1) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
long diff = cur_time - timeout_start;
|
||||
if (diff >= timeout) {
|
||||
// We have timed out
|
||||
App.setShutdown();
|
||||
if ( App.getDB().Loaded() ) {
|
||||
App.setShutdown(act.getString(R.string.app_timeout));
|
||||
LockingActivity.checkShutdown(act);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void checkShutdown(Activity act) {
|
||||
if ( App.isShutdown() && App.getDB().Loaded() ) {
|
||||
act.setResult(PasswordActivity.RESULT_EXIT_LOCK);
|
||||
act.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.services;
|
||||
package com.keepassdroid.timeout;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
@@ -30,7 +30,6 @@ import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
|
||||
public class TimeoutService extends Service {
|
||||
private static final String TAG = "KeePassDroid Timer";
|
||||
@@ -44,42 +43,36 @@ public class TimeoutService extends Service {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if ( action.equals(Intents.TIMEOUT) ) {
|
||||
timeout(context);
|
||||
if ( action != null && action.equals(Timeout.TIMEOUT) ) {
|
||||
timeout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intents.TIMEOUT);
|
||||
filter.addAction(Timeout.TIMEOUT);
|
||||
registerReceiver(mIntentReceiver, filter);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
super.onStart(intent, startId);
|
||||
|
||||
Log.d(TAG, "Timeout service started");
|
||||
}
|
||||
|
||||
private void timeout(Context context) {
|
||||
private void timeout() {
|
||||
Log.d(TAG, "Timeout");
|
||||
App.setShutdown();
|
||||
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.cancelAll();
|
||||
|
||||
if (nm != null)
|
||||
nm.cancelAll();
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
Log.d(TAG, "Timeout service stopped");
|
||||
|
||||
unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ package com.keepassdroid.utils;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.keepassdroid.database.PwDate;
|
||||
import com.keepassdroid.database.PwEntryV3;
|
||||
|
||||
import static com.keepassdroid.database.PwDate.DEFAULT_PWDATE;
|
||||
|
||||
public class EmptyUtils {
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
@@ -34,7 +35,7 @@ public class EmptyUtils {
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(PwDate date) {
|
||||
return (date == null) || date.equals(PwEntryV3.DEFAULT_PWDATE);
|
||||
return (date == null) || date.equals(DEFAULT_PWDATE);
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(Uri uri) {
|
||||
|
||||
@@ -7,11 +7,11 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.activities.AboutActivity;
|
||||
import com.keepassdroid.settings.SettingsActivity;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
|
||||
public class MenuUtil {
|
||||
@@ -37,13 +37,20 @@ public class MenuUtil {
|
||||
}
|
||||
|
||||
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
|
||||
return onDefaultMenuOptionsItemSelected(activity, item, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param checkLock Check the time lock before launch settings in LockingActivity
|
||||
*/
|
||||
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_donate:
|
||||
return onDonationItemSelected(activity);
|
||||
|
||||
case R.id.menu_app_settings:
|
||||
Intent i = new Intent(activity, SettingsActivity.class);
|
||||
activity.startActivity(i);
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
SettingsActivity.launch(activity, checkLock);
|
||||
return true;
|
||||
|
||||
case R.id.menu_about:
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.utils;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SpannableReplacer {
|
||||
private final CharSequence mSource;
|
||||
private final CharSequence mReplacement;
|
||||
private final Matcher mMatcher;
|
||||
private int mAppendPosition;
|
||||
private final boolean mIsSpannable;
|
||||
|
||||
public static CharSequence replace(CharSequence source, String regex,
|
||||
CharSequence replacement) {
|
||||
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(source);
|
||||
return new SpannableReplacer(source, matcher, replacement).doReplace();
|
||||
}
|
||||
|
||||
private SpannableReplacer(CharSequence source, Matcher matcher,
|
||||
CharSequence replacement) {
|
||||
mSource = source;
|
||||
mReplacement = replacement;
|
||||
mMatcher = matcher;
|
||||
mAppendPosition = 0;
|
||||
mIsSpannable = replacement instanceof Spannable;
|
||||
}
|
||||
|
||||
private CharSequence doReplace() {
|
||||
SpannableStringBuilder buffer = new SpannableStringBuilder();
|
||||
while (mMatcher.find()) {
|
||||
appendReplacement(buffer);
|
||||
}
|
||||
return appendTail(buffer);
|
||||
}
|
||||
|
||||
private void appendReplacement(SpannableStringBuilder buffer) {
|
||||
buffer.append(mSource.subSequence(mAppendPosition, mMatcher.start()));
|
||||
CharSequence replacement = mIsSpannable
|
||||
? copyCharSequenceWithSpans(mReplacement)
|
||||
: mReplacement;
|
||||
buffer.append(replacement);
|
||||
|
||||
mAppendPosition = mMatcher.end();
|
||||
}
|
||||
|
||||
public SpannableStringBuilder appendTail(SpannableStringBuilder buffer) {
|
||||
buffer.append(mSource.subSequence(mAppendPosition, mSource.length()));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// This is a weird way of copying spans, but I don't know any better way.
|
||||
private CharSequence copyCharSequenceWithSpans(CharSequence string) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
TextUtils.writeToParcel(string, parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.utils;
|
||||
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
|
||||
public class SprEngine {
|
||||
|
||||
private static SprEngineV4 sprV4 = new SprEngineV4();
|
||||
private static SprEngine spr = new SprEngine();
|
||||
|
||||
public static SprEngine getInstance(PwDatabase db) {
|
||||
if (db instanceof PwDatabaseV4) {
|
||||
return sprV4;
|
||||
}
|
||||
else {
|
||||
return spr;
|
||||
}
|
||||
}
|
||||
|
||||
public String compile(String text, PwEntry entry, PwDatabase database) {
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.SearchParametersV4;
|
||||
|
||||
public class SprEngineV4 extends SprEngine {
|
||||
public class SprEngineV4 {
|
||||
private final int MAX_RECURSION_DEPTH = 12;
|
||||
private final String STR_REF_START = "{REF:";
|
||||
private final String STR_REF_END = "}";
|
||||
@@ -45,7 +45,6 @@ public class SprEngineV4 extends SprEngine {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String compile(String text, PwEntry entry, PwDatabase database) {
|
||||
SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, (PwEntryV4)entry);
|
||||
|
||||
|
||||
@@ -24,36 +24,20 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.text.ClipboardManager;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keepassdroid.database.exception.SamsungClipboardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class Util {
|
||||
public static String getClipboard(Context context) {
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
CharSequence csText = clipboard.getText();
|
||||
if (csText == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return csText.toString();
|
||||
}
|
||||
|
||||
public static void copyToClipboard(Context context, String text) throws SamsungClipboardException {
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
||||
try {
|
||||
clipboard.setText(text);
|
||||
} catch (NullPointerException e) {
|
||||
throw new SamsungClipboardException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void gotoUrl(Context context, String url) throws ActivityNotFoundException {
|
||||
if ( url != null && url.length() > 0 ) {
|
||||
Uri uri = Uri.parse(url);
|
||||
@@ -73,9 +57,66 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyFontVisibilityToTextView(boolean applyMonospace, TextView textView) {
|
||||
if (applyMonospace)
|
||||
textView.setTypeface(Typeface.MONOSPACE);
|
||||
private final static String stringToStrikeThrough = "0";
|
||||
|
||||
/**
|
||||
* Replace font by monospace and strike through all zeros, must be called after seText()
|
||||
*/
|
||||
public static void applyFontVisibilityTo(final TextView textView) {
|
||||
textView.setText(strikeThroughToZero(textView.getText()));
|
||||
textView.setTypeface(Typeface.MONOSPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace font by monospace and strike through all zeros, must be called after seText()
|
||||
*/
|
||||
public static void applyFontVisibilityTo(final EditText editText) {
|
||||
// Assign spans to default text
|
||||
editText.setText(strikeThroughToZero(editText.getText()));
|
||||
// Add spans for each new 0 character
|
||||
class TextWatcherCustomFont implements TextWatcher {
|
||||
|
||||
private boolean applySpannable;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||
applySpannable = count < after;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (applySpannable) {
|
||||
String text = charSequence.toString();
|
||||
if (text.contains(stringToStrikeThrough)) {
|
||||
for (int index = text.indexOf(stringToStrikeThrough);
|
||||
index >= 0; index = text.indexOf(stringToStrikeThrough,
|
||||
index + 1)) {
|
||||
editText.getText().setSpan(new StrikethroughSpan(),
|
||||
index,
|
||||
index + stringToStrikeThrough.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {}
|
||||
};
|
||||
TextWatcher textWatcher = new TextWatcherCustomFont();
|
||||
editText.addTextChangedListener(textWatcher);
|
||||
editText.setTypeface(Typeface.MONOSPACE);
|
||||
}
|
||||
|
||||
private static CharSequence strikeThroughToZero(final CharSequence text) {
|
||||
if (text.toString().contains(stringToStrikeThrough)) {
|
||||
SpannableString spannable = new SpannableString(stringToStrikeThrough);
|
||||
spannable.setSpan(new StrikethroughSpan(),
|
||||
0,
|
||||
stringToStrikeThrough.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return SpannableReplacer.replace(text, stringToStrikeThrough, spannable);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
@@ -36,15 +37,15 @@ import android.widget.RelativeLayout;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
public class AddNodeButtonView extends RelativeLayout {
|
||||
|
||||
private enum State {
|
||||
OPEN, CLOSE
|
||||
}
|
||||
|
||||
private FloatingActionButton addButton;
|
||||
private View addEntry;
|
||||
private View addGroup;
|
||||
private FloatingActionButton addButtonView;
|
||||
private View addEntryView;
|
||||
private View addGroupView;
|
||||
|
||||
private boolean addEntryEnable;
|
||||
private boolean addGroupEnable;
|
||||
@@ -58,11 +59,11 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
private ViewMenuAnimation viewMenuAnimationAddGroup;
|
||||
private long animationDuration;
|
||||
|
||||
public ListNodesWithAddButtonView(Context context) {
|
||||
public AddNodeButtonView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ListNodesWithAddButtonView(Context context, AttributeSet attrs) {
|
||||
public AddNodeButtonView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
inflate(context);
|
||||
}
|
||||
@@ -70,83 +71,90 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
protected void inflate(Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
assert inflater != null;
|
||||
inflater.inflate(R.layout.list_nodes_with_add_button, this);
|
||||
inflater.inflate(R.layout.add_node_button, this);
|
||||
|
||||
addEntryEnable = true;
|
||||
addGroupEnable = true;
|
||||
|
||||
addButton = (FloatingActionButton) findViewById(R.id.add_button);
|
||||
addEntry = findViewById(R.id.add_entry);
|
||||
addGroup = findViewById(R.id.add_group);
|
||||
addButtonView = findViewById(R.id.add_button);
|
||||
addEntryView = findViewById(R.id.add_entry);
|
||||
addGroupView = findViewById(R.id.add_group);
|
||||
|
||||
animationDuration = 300L;
|
||||
|
||||
viewButtonMenuAnimation = new AddButtonAnimation(addButton);
|
||||
viewMenuAnimationAddEntry = new ViewMenuAnimation(addEntry, 0L, 150L);
|
||||
viewMenuAnimationAddGroup = new ViewMenuAnimation(addGroup, 150L, 0L);
|
||||
viewButtonMenuAnimation = new AddButtonAnimation(addButtonView);
|
||||
viewMenuAnimationAddEntry = new ViewMenuAnimation(addEntryView, 0L, 150L);
|
||||
viewMenuAnimationAddGroup = new ViewMenuAnimation(addGroupView, 150L, 0L);
|
||||
|
||||
allowAction = true;
|
||||
state = State.CLOSE;
|
||||
|
||||
onAddButtonClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (allowAction && state.equals(State.CLOSE)) {
|
||||
startGlobalAnimation();
|
||||
}
|
||||
}
|
||||
};
|
||||
addButton.setOnClickListener(onAddButtonClickListener);
|
||||
onAddButtonClickListener = v -> startGlobalAnimation();
|
||||
addButtonView.setOnClickListener(onAddButtonClickListener);
|
||||
|
||||
onAddButtonVisibilityChangedListener = new FloatingActionButton.OnVisibilityChangedListener() {
|
||||
@Override
|
||||
public void onHidden(FloatingActionButton fab) {
|
||||
super.onHidden(fab);
|
||||
addButton.setOnClickListener(null);
|
||||
addButton.setClickable(false);
|
||||
addButtonView.setOnClickListener(null);
|
||||
addButtonView.setClickable(false);
|
||||
}
|
||||
@Override
|
||||
public void onShown(FloatingActionButton fab) {
|
||||
super.onShown(fab);
|
||||
addButton.setOnClickListener(onAddButtonClickListener);
|
||||
addButtonView.setOnClickListener(onAddButtonClickListener);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Hide when scroll
|
||||
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.nodes_list);
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
Rect viewButtonRect = new Rect(),
|
||||
viewEntryRect = new Rect(),
|
||||
viewGroupRect = new Rect();
|
||||
addButtonView.getGlobalVisibleRect(viewButtonRect);
|
||||
addEntryView.getGlobalVisibleRect(viewEntryRect);
|
||||
addGroupView.getGlobalVisibleRect(viewGroupRect);
|
||||
if (! (viewButtonRect.contains((int) event.getRawX(), (int) event.getRawY())
|
||||
&& viewEntryRect.contains((int) event.getRawX(), (int) event.getRawY())
|
||||
&& viewGroupRect.contains((int) event.getRawX(), (int) event.getRawY()) )) {
|
||||
closeButtonIfOpen();
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public RecyclerView.OnScrollListener hideButtonOnScrollListener() {
|
||||
return new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
if (state.equals(State.CLOSE)) {
|
||||
if (dy > 0 && addButton.getVisibility() == View.VISIBLE) {
|
||||
if (dy > 0 && addButtonView.getVisibility() == View.VISIBLE) {
|
||||
hideButton();
|
||||
} else if (dy < 0 && addButton.getVisibility() != View.VISIBLE) {
|
||||
} else if (dy < 0 && addButtonView.getVisibility() != View.VISIBLE) {
|
||||
showButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void showButton() {
|
||||
addButton.show(onAddButtonVisibilityChangedListener);
|
||||
addButtonView.show(onAddButtonVisibilityChangedListener);
|
||||
}
|
||||
|
||||
public void hideButton() {
|
||||
addButton.hide(onAddButtonVisibilityChangedListener);
|
||||
addButtonView.hide(onAddButtonVisibilityChangedListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
Rect viewRectG = new Rect();
|
||||
getGlobalVisibleRect(viewRectG);
|
||||
if (viewRectG.contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||
if(allowAction && state.equals(State.OPEN)) {
|
||||
startGlobalAnimation();
|
||||
}
|
||||
/**
|
||||
* Start the animation to close the button
|
||||
*/
|
||||
public void closeButtonIfOpen() {
|
||||
if(state.equals(State.OPEN)) {
|
||||
startGlobalAnimation();
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,8 +163,8 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
*/
|
||||
public void enableAddEntry(boolean enable) {
|
||||
this.addEntryEnable = enable;
|
||||
if (enable && addEntry != null && addEntry.getVisibility() != VISIBLE)
|
||||
addEntry.setVisibility(INVISIBLE);
|
||||
if (enable && addEntryView != null && addEntryView.getVisibility() != VISIBLE)
|
||||
addEntryView.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,28 +173,36 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
*/
|
||||
public void enableAddGroup(boolean enable) {
|
||||
this.addGroupEnable = enable;
|
||||
if (enable && addGroup != null && addGroup.getVisibility() != VISIBLE)
|
||||
addGroup.setVisibility(INVISIBLE);
|
||||
if (enable && addGroupView != null && addGroupView.getVisibility() != VISIBLE)
|
||||
addGroupView.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
public void setAddGroupClickListener(OnClickListener onClickListener) {
|
||||
if (addEntryEnable)
|
||||
addGroup.setOnClickListener(onClickListener);
|
||||
if (addGroupEnable)
|
||||
addGroupView.setOnClickListener(view -> {
|
||||
onClickListener.onClick(view);
|
||||
closeButtonIfOpen();
|
||||
});
|
||||
}
|
||||
|
||||
public void setAddEntryClickListener(OnClickListener onClickListener) {
|
||||
if (addGroupEnable)
|
||||
addEntry.setOnClickListener(onClickListener);
|
||||
if (addEntryEnable)
|
||||
addEntryView.setOnClickListener(view -> {
|
||||
onClickListener.onClick(view);
|
||||
closeButtonIfOpen();
|
||||
});
|
||||
}
|
||||
|
||||
private void startGlobalAnimation() {
|
||||
viewButtonMenuAnimation.startAnimation();
|
||||
if (allowAction) {
|
||||
viewButtonMenuAnimation.startAnimation();
|
||||
|
||||
if (addEntryEnable) {
|
||||
viewMenuAnimationAddEntry.startAnimation();
|
||||
}
|
||||
if (addGroupEnable) {
|
||||
viewMenuAnimationAddGroup.startAnimation();
|
||||
if (addEntryEnable) {
|
||||
viewMenuAnimationAddEntry.startAnimation();
|
||||
}
|
||||
if (addGroupEnable) {
|
||||
viewMenuAnimationAddGroup.startAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,10 +276,6 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
this.delayOut = delayOut;
|
||||
}
|
||||
|
||||
ViewMenuAnimation(View view) {
|
||||
this(view, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(View view) {}
|
||||
|
||||
@@ -296,7 +308,7 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
|
||||
} else {
|
||||
// The first time
|
||||
if (translation == 0) {
|
||||
translation = view.getY() + view.getHeight()/2 - addButton.getY() - addButton.getHeight()/2;
|
||||
translation = view.getY() + view.getHeight()/2 - addButtonView.getY() - addButtonView.getHeight()/2;
|
||||
view.setTranslationY(-translation);
|
||||
view.setTranslationX(view.getWidth()/3);
|
||||
view.setAlpha(0.0F);
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
|
||||
public abstract class ClickView extends LinearLayout {
|
||||
protected boolean readOnly = false;
|
||||
|
||||
public ClickView(Context context) {
|
||||
super(context);
|
||||
|
||||
readOnly = App.getDB().readOnly;
|
||||
}
|
||||
|
||||
abstract public void onClick();
|
||||
|
||||
abstract public void onCreateMenu(ContextMenu menu, ContextMenuInfo menuInfo);
|
||||
|
||||
abstract public boolean onContextItemSelected(MenuItem item);
|
||||
}
|
||||
@@ -84,25 +84,25 @@ public class EntryContentsView extends LinearLayout {
|
||||
inflater.inflate(R.layout.entry_view_contents, this);
|
||||
|
||||
userNameContainerView = findViewById(R.id.entry_user_name_container);
|
||||
userNameView = (TextView) findViewById(R.id.entry_user_name);
|
||||
userNameActionView = (ImageView) findViewById(R.id.entry_user_name_action_image);
|
||||
userNameView = findViewById(R.id.entry_user_name);
|
||||
userNameActionView = findViewById(R.id.entry_user_name_action_image);
|
||||
|
||||
passwordContainerView = findViewById(R.id.entry_password_container);
|
||||
passwordView = (TextView) findViewById(R.id.entry_password);
|
||||
passwordActionView = (ImageView) findViewById(R.id.entry_password_action_image);
|
||||
passwordView = findViewById(R.id.entry_password);
|
||||
passwordActionView = findViewById(R.id.entry_password_action_image);
|
||||
|
||||
urlContainerView = findViewById(R.id.entry_url_container);
|
||||
urlView = (TextView) findViewById(R.id.entry_url);
|
||||
urlView = findViewById(R.id.entry_url);
|
||||
|
||||
commentContainerView = findViewById(R.id.entry_comment_container);
|
||||
commentView = (TextView) findViewById(R.id.entry_comment);
|
||||
commentView = findViewById(R.id.entry_comment);
|
||||
|
||||
extrasView = (ViewGroup) findViewById(R.id.extra_strings);
|
||||
extrasView = findViewById(R.id.extra_strings);
|
||||
|
||||
creationDateView = (TextView) findViewById(R.id.entry_created);
|
||||
modificationDateView = (TextView) findViewById(R.id.entry_modified);
|
||||
lastAccessDateView = (TextView) findViewById(R.id.entry_accessed);
|
||||
expiresDateView = (TextView) findViewById(R.id.entry_expires);
|
||||
creationDateView = findViewById(R.id.entry_created);
|
||||
modificationDateView = findViewById(R.id.entry_modified);
|
||||
lastAccessDateView = findViewById(R.id.entry_accessed);
|
||||
expiresDateView = findViewById(R.id.entry_expires);
|
||||
}
|
||||
|
||||
public void applyFontVisibilityToFields(boolean fontInVisibility) {
|
||||
@@ -113,6 +113,10 @@ public class EntryContentsView extends LinearLayout {
|
||||
if (userName != null && !userName.isEmpty()) {
|
||||
userNameContainerView.setVisibility(VISIBLE);
|
||||
userNameView.setText(userName);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(userNameView);
|
||||
} else {
|
||||
userNameContainerView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,11 +128,17 @@ public class EntryContentsView extends LinearLayout {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
passwordContainerView.setVisibility(VISIBLE);
|
||||
passwordView.setText(password);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(passwordView);
|
||||
passwordActionView.setVisibility(GONE);
|
||||
} else {
|
||||
passwordContainerView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void assignPasswordCopyListener(OnClickListener onClickListener) {
|
||||
passwordActionView.setOnClickListener(onClickListener);
|
||||
passwordActionView.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
public boolean isPasswordPresent() {
|
||||
@@ -147,21 +157,25 @@ public class EntryContentsView extends LinearLayout {
|
||||
if (url != null && !url.isEmpty()) {
|
||||
urlContainerView.setVisibility(VISIBLE);
|
||||
urlView.setText(url);
|
||||
} else {
|
||||
urlContainerView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void assignComment(String comment) {
|
||||
if (comment != null && !comment.isEmpty()) {
|
||||
commentContainerView.setVisibility(VISIBLE);
|
||||
Util.applyFontVisibilityToTextView(fontInVisibility, commentView);
|
||||
commentView.setText(comment);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(commentView);
|
||||
} else {
|
||||
commentContainerView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void addExtraField(String title, String value, OnClickListener onActionClickListener) {
|
||||
EntryNewField entryNewField = new EntryNewField(getContext(), null, title, value, onActionClickListener);
|
||||
entryNewField.applyFontVisibilityToValue(fontInVisibility);
|
||||
entryNewField.applyFontVisibility(fontInVisibility);
|
||||
extrasView.addView(entryNewField);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -36,7 +37,7 @@ import com.kunzisoft.keepass.R;
|
||||
public class EntryEditNewField extends RelativeLayout {
|
||||
|
||||
private TextView labelView;
|
||||
private TextView valueView;
|
||||
private EditText valueView;
|
||||
private CompoundButton protectionCheckView;
|
||||
|
||||
public EntryEditNewField(Context context) {
|
||||
@@ -55,16 +56,11 @@ public class EntryEditNewField extends RelativeLayout {
|
||||
inflater.inflate(R.layout.entry_edit_new_field, this);
|
||||
|
||||
View deleteView = findViewById(R.id.entry_edit_new_field_delete);
|
||||
deleteView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
deleteViewFromParent();
|
||||
}
|
||||
});
|
||||
deleteView.setOnClickListener(v -> deleteViewFromParent());
|
||||
|
||||
labelView = (TextView) findViewById(R.id.entry_edit_new_field_label);
|
||||
valueView = (TextView) findViewById(R.id.entry_edit_new_field_value);
|
||||
protectionCheckView = (CompoundButton) findViewById(R.id.protection);
|
||||
labelView = findViewById(R.id.entry_edit_new_field_label);
|
||||
valueView = findViewById(R.id.entry_edit_new_field_value);
|
||||
protectionCheckView = findViewById(R.id.protection);
|
||||
}
|
||||
|
||||
public void setData(String label, ProtectedString value) {
|
||||
@@ -89,7 +85,8 @@ public class EntryEditNewField extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void setFontVisibility(boolean applyFontVisibility) {
|
||||
Util.applyFontVisibilityToTextView(applyFontVisibility, valueView);
|
||||
if (applyFontVisibility)
|
||||
Util.applyFontVisibilityTo(valueView);
|
||||
}
|
||||
|
||||
public void deleteViewFromParent() {
|
||||
|
||||
@@ -54,17 +54,18 @@ public class EntryNewField extends LinearLayout {
|
||||
assert inflater != null;
|
||||
inflater.inflate(R.layout.entry_new_field, this);
|
||||
|
||||
labelView = (TextView) findViewById(R.id.title);
|
||||
valueView = (TextView) findViewById(R.id.value);
|
||||
actionImageView = (ImageView) findViewById(R.id.action_image);
|
||||
labelView = findViewById(R.id.title);
|
||||
valueView = findViewById(R.id.value);
|
||||
actionImageView = findViewById(R.id.action_image);
|
||||
|
||||
setLabel(label);
|
||||
setValue(value);
|
||||
setAction(onClickActionListener);
|
||||
}
|
||||
|
||||
public void applyFontVisibilityToValue(boolean changeFont) {
|
||||
Util.applyFontVisibilityToTextView(changeFont, valueView);
|
||||
public void applyFontVisibility(boolean fontInVisibility) {
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(valueView);
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class NoFocusScrollView extends ScrollView {
|
||||
|
||||
public NoFocusScrollView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public NoFocusScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NoFocusScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<View> getFocusables(int direction) {
|
||||
return new ArrayList<View>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.method.ArrowKeyMovementMethod;
|
||||
import android.text.method.MovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class TextViewSelect extends TextView {
|
||||
|
||||
public TextViewSelect(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextViewSelect(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.textViewStyle);
|
||||
}
|
||||
|
||||
public TextViewSelect(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected MovementMethod getDefaultMovementMethod() {
|
||||
return ArrowKeyMovementMethod.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getDefaultEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user