Merge branch 'release/2.5.0.0beta7'

This commit is contained in:
J-Jamet
2018-03-25 20:40:00 +02:00
149 changed files with 3719 additions and 3862 deletions

View File

@@ -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) KeepassDX (2.5.0.0beta5)
* Autofill (Android O) * Autofill (Android O)
* Deletion for group * Deletion for group

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 27 targetSdkVersion 27
versionCode = 6 versionCode = 7
versionName = "2.5.0.0beta6" versionName = "2.5.0.0beta7"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.keepassdroid.tests" testApplicationId = "com.keepassdroid.tests"

View File

@@ -42,7 +42,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
} }
public void testName() { 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 { public void testPassword() throws UnsupportedEncodingException {
@@ -54,7 +54,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
public void testCreation() { public void testCreation() {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTime(mPE.tCreation.getJDate()); cal.setTime(mPE.getCreationTime().getDate());
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009); assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3); assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);

View File

@@ -19,10 +19,7 @@
*/ */
package com.keepassdroid.tests; package com.keepassdroid.tests;
import java.util.UUID; import com.keepassdroid.database.AutoType;
import junit.framework.TestCase;
import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroupV4; import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.PwIconCustom; 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.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.database.security.ProtectedString;
import junit.framework.TestCase;
import java.util.UUID;
public class PwEntryTestV4 extends TestCase { public class PwEntryTestV4 extends TestCase {
public void testAssign() { public void testAssign() {
PwEntryV4 entry = new PwEntryV4(); PwEntryV4 entry = new PwEntryV4();
entry.additional = "test223"; entry.setAdditional("test223");
entry.autoType = entry.new AutoType(); entry.setAutoType(new AutoType());
entry.autoType.defaultSequence = "1324"; entry.getAutoType().defaultSequence = "1324";
entry.autoType.enabled = true; entry.getAutoType().enabled = true;
entry.autoType.obfuscationOptions = 123412432109L; entry.getAutoType().obfuscationOptions = 123412432109L;
entry.autoType.put("key", "value"); entry.getAutoType().put("key", "value");
entry.backgroupColor = "blue"; entry.setBackgroupColor("blue");
entry.binaries.put("key1", new ProtectedBinary(false, new byte[] {0,1})); entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
entry.customIcon = new PwIconCustom(UUID.randomUUID(), new byte[0]); entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.foregroundColor = "red"; entry.setForegroundColor("red");
entry.history.add(new PwEntryV4()); entry.addToHistory(new PwEntryV4());
entry.icon = new PwIconStandard(5); entry.setIcon(new PwIconStandard(5));
entry.overrideURL = "override"; entry.setOverrideURL("override");
entry.parent = new PwGroupV4(); entry.setParent(new PwGroupV4());
entry.addField("key2", new ProtectedString(false, "value2")); entry.addField("key2", new ProtectedString(false, "value2"));
entry.url = "http://localhost"; entry.setUrl("http://localhost");
entry.uuid = UUID.randomUUID(); entry.setUUID(UUID.randomUUID());
PwEntryV4 target = new PwEntryV4(); PwEntryV4 target = new PwEntryV4();
target.assign(entry); target.assign(entry);

View File

@@ -38,7 +38,7 @@ public class PwGroupTest extends AndroidTestCase {
} }
public void testGroupName() { public void testGroupName() {
assertTrue("Name was " + mPG.name, mPG.name.equals("Internet")); assertTrue("Name was " + mPG.getName(), mPG.getName().equals("Internet"));
} }
} }

View File

@@ -32,19 +32,21 @@ public class EntryV4 extends TestCase {
db.historyMaxItems = 2; db.historyMaxItems = 2;
PwEntryV4 entry = new PwEntryV4(); PwEntryV4 entry = new PwEntryV4();
entry.setTitle("Title1", db); entry.startToDecodeReference(db);
entry.setUsername("User1", db); entry.setTitle("Title1");
entry.setUsername("User1");
entry.createBackup(db); entry.createBackup(db);
entry.setTitle("Title2", db); entry.setTitle("Title2");
entry.setUsername("User2", db); entry.setUsername("User2");
entry.createBackup(db); entry.createBackup(db);
entry.setTitle("Title3", db); entry.setTitle("Title3");
entry.setUsername("User3", db); entry.setUsername("User3");
entry.createBackup(db); entry.createBackup(db);
PwEntryV4 backup = entry.history.get(0); PwEntryV4 backup = entry.getHistory().get(0);
entry.endToDecodeReference(db);
assertEquals("Title2", backup.getTitle()); assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername()); assertEquals("User2", backup.getUsername());
} }

View File

@@ -27,15 +27,16 @@ import android.content.res.AssetManager;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import biz.source_code.base64Coder.Base64Coder; import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV4; import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.load.ImporterV4; import com.keepassdroid.database.load.ImporterV4;
import com.keepassdroid.utils.SprEngine; import com.keepassdroid.utils.SprEngineV4;
import com.keepassdroid.utils.Types; import com.keepassdroid.utils.Types;
public class SprEngineTest extends AndroidTestCase { public class SprEngineTest extends AndroidTestCase {
private PwDatabaseV4 db; private PwDatabaseV4 db;
private SprEngine spr; private SprEngineV4 spr;
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
@@ -51,7 +52,7 @@ public class SprEngineTest extends AndroidTestCase {
is.close(); is.close();
spr = SprEngine.getInstance(db); spr = new SprEngineV4();
} }
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}"; private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
@@ -69,7 +70,7 @@ public class SprEngineTest extends AndroidTestCase {
private UUID decodeUUID(String encoded) { private UUID decodeUUID(String encoded) {
if (encoded == null || encoded.length() == 0 ) { if (encoded == null || encoded.length() == 0 ) {
return PwDatabaseV4.UUID_ZERO; return PwDatabase.UUID_ZERO;
} }
byte[] buf = Base64Coder.decode(encoded); byte[] buf = Base64Coder.decode(encoded);

View File

@@ -25,7 +25,6 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import com.kunzisoft.keepass.R;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwGroup; import com.keepassdroid.database.PwGroup;
import com.keepassdroid.tests.database.TestData; import com.keepassdroid.tests.database.TestData;
@@ -66,7 +65,7 @@ public class SearchTest extends AndroidTestCase {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(ctx.getString(R.string.settings_omitbackup_key), setting); editor.putBoolean("settings_omitbackup_key", setting);
editor.commit(); editor.commit();
} }

View File

@@ -133,8 +133,13 @@
<activity android:name="com.keepassdroid.settings.SettingsActivity" /> <activity android:name="com.keepassdroid.settings.SettingsActivity" />
<activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity" <activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" /> 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 <service
android:name="com.keepassdroid.autofill.KeeAutofillService" android:name="com.keepassdroid.autofill.KeeAutofillService"
android:label="@string/autofill_service_name" android:label="@string/autofill_service_name"

View File

@@ -21,26 +21,12 @@
package com.keepassdroid.activities; package com.keepassdroid.activities;
import android.app.Activity; 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.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; 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.support.v7.widget.Toolbar;
import android.text.SpannableString; import android.util.Log;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@@ -50,15 +36,13 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.compat.ActivityCompat;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.exception.SamsungClipboardException; import com.keepassdroid.notifications.NotificationCopyingService;
import com.keepassdroid.intents.Intents; import com.keepassdroid.notifications.NotificationField;
import com.keepassdroid.settings.PreferencesUtil; import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.password.PasswordActivity; import com.keepassdroid.timeout.ClipboardHelper;
import com.keepassdroid.tasks.UIToastTask;
import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.MenuUtil; import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.Types; import com.keepassdroid.utils.Types;
@@ -66,35 +50,35 @@ import com.keepassdroid.utils.Util;
import com.keepassdroid.view.EntryContentsView; import com.keepassdroid.view.EntryContentsView;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID; import java.util.UUID;
import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable; import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable;
public class EntryActivity extends LockingHideActivity { 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 String KEY_ENTRY = "entry";
public static final int NOTIFY_PASSWORD = 2;
private ImageView titleIconView; private ImageView titleIconView;
private TextView titleView; private TextView titleView;
private EntryContentsView entryContentsView; private EntryContentsView entryContentsView;
protected PwEntry mEntry; protected PwEntry mEntry;
private Timer mTimer = new Timer();
private boolean mShowPassword; private boolean mShowPassword;
private NotificationManager mNM;
private BroadcastReceiver mIntentReceiver;
protected boolean readOnly = false; protected boolean readOnly = false;
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
public static void launch(Activity act, PwEntry pw) { public static void launch(Activity act, PwEntry pw) {
Intent intent = new Intent(act, EntryActivity.class); if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID())); Intent intent = new Intent(act, EntryActivity.class);
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE); intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
} }
@Override @Override
@@ -103,7 +87,7 @@ public class EntryActivity extends LockingHideActivity {
setContentView(R.layout.entry_view); setContentView(R.layout.entry_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); 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 // Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
ActivityCompat.invalidateOptionsMenu(this); invalidateOptionsMenu();
// Update last access time. // Update last access time.
mEntry.touch(false, false); mEntry.touch(false, false);
// Get views // Get views
titleIconView = (ImageView) findViewById(R.id.entry_icon); titleIconView = findViewById(R.id.entry_icon);
titleView = (TextView) findViewById(R.id.entry_title); titleView = findViewById(R.id.entry_title);
entryContentsView = (EntryContentsView) findViewById(R.id.entry_contents); entryContentsView = findViewById(R.id.entry_contents);
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this)); entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
fillData();
// Setup Edit Buttons // Setup Edit Buttons
View edit = findViewById(R.id.entry_edit); View edit = findViewById(R.id.entry_edit);
edit.setOnClickListener(new View.OnClickListener() { edit.setOnClickListener(v -> EntryEditActivity.Launch(EntryActivity.this, mEntry));
public void onClick(View v) {
EntryEditActivity.Launch(EntryActivity.this, mEntry);
}
});
if (readOnly) { if (readOnly) {
edit.setVisibility(View.GONE); edit.setVisibility(View.GONE);
} }
// If notifications enabled in settings // Init the clipboard helper
if (isClipboardNotificationsEnable(getApplicationContext())) { clipboardHelper = new ClipboardHelper(this);
// Notification Manager firstLaunchOfActivity = true;
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); }
if (mEntry.getPassword().length() > 0) { @Override
// only show notification if password is available protected void onResume() {
Notification password = getNotification(Intents.COPY_PASSWORD, R.string.copy_password); super.onResume();
mNM.notify(NOTIFY_PASSWORD, password);
}
if (mEntry.getUsername().length() > 0) { // Fill data in resume to update from EntryEditActivity
// only show notification if username is available fillData();
Notification username = getNotification(Intents.COPY_USERNAME, R.string.copy_username); invalidateOptionsMenu();
mNM.notify(NOTIFY_USERNAME, username);
// 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
} }
firstLaunchOfActivity = false;
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;
}
private void populateTitle(Drawable drawIcon, String text) { private void populateTitle(Drawable drawIcon, String text) {
titleIconView.setImageDrawable(drawIcon); titleIconView.setImageDrawable(drawIcon);
@@ -247,59 +214,55 @@ public class EntryActivity extends LockingHideActivity {
Database db = App.getDB(); Database db = App.getDB();
PwDatabase pm = db.pm; PwDatabase pm = db.pm;
mEntry.startToDecodeReference(pm);
// Assign title // Assign title
populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()), populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()),
mEntry.getTitle(true, pm)); mEntry.getTitle());
// Assign basic fields // Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername(true, pm)); entryContentsView.assignUserName(mEntry.getUsername());
entryContentsView.assignUserNameCopyListener(new View.OnClickListener() { entryContentsView.assignUserNameCopyListener(view ->
@Override clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
public void onClick(View view) { getString(R.string.copy_field, getString(R.string.entry_user_name)))
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm), );
getString(R.string.copy_field, getString(R.string.entry_user_name)));
}
});
entryContentsView.assignPassword(mEntry.getPassword(true, pm)); entryContentsView.assignPassword(mEntry.getPassword());
entryContentsView.assignPasswordCopyListener(new View.OnClickListener() { if (PreferencesUtil.allowCopyPassword(this)) {
@Override entryContentsView.assignPasswordCopyListener(view ->
public void onClick(View view) { clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm), getString(R.string.copy_field, getString(R.string.entry_password)))
getString(R.string.copy_field, getString(R.string.entry_password))); );
} }
});
entryContentsView.assignURL(mEntry.getUrl(true, pm)); entryContentsView.assignURL(mEntry.getUrl());
entryContentsView.setHiddenPasswordStyle(!mShowPassword); entryContentsView.setHiddenPasswordStyle(!mShowPassword);
entryContentsView.assignComment(mEntry.getNotes(true, pm)); entryContentsView.assignComment(mEntry.getNotes());
// Assign custom fields // Assign custom fields
if (mEntry.allowExtraFields()) { if (mEntry.allowExtraFields()) {
entryContentsView.clearExtraFields(); 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 label = field.getKey();
final String value = field.getValue(); final String value = field.getValue();
entryContentsView.addExtraField(label, value, new View.OnClickListener() { entryContentsView.addExtraField(label, value, view ->
@Override clipboardHelper.timeoutCopyToClipboard(value, getString(R.string.copy_field, label)));
public void onClick(View view) {
timeoutCopyToClipboard(value, getString(R.string.copy_field, label));
}
});
} }
} }
// Assign dates // Assign dates
entryContentsView.assignCreationDate(mEntry.getCreationTime()); entryContentsView.assignCreationDate(mEntry.getCreationTime().getDate());
entryContentsView.assignModificationDate(mEntry.getLastModificationTime()); entryContentsView.assignModificationDate(mEntry.getLastModificationTime().getDate());
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime()); entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime().getDate());
Date expires = mEntry.getExpiryTime(); Date expires = mEntry.getExpiryTime().getDate();
if ( mEntry.expires() ) { if ( mEntry.expires() ) {
entryContentsView.assignExpiresDate(expires); entryContentsView.assignExpiresDate(expires);
} else { } else {
entryContentsView.assignExpiresDate(getString(R.string.never)); entryContentsView.assignExpiresDate(getString(R.string.never));
} }
mEntry.endToDecodeReference(pm);
} }
@Override @Override
@@ -332,26 +295,28 @@ public class EntryActivity extends LockingHideActivity {
inflater.inflate(R.menu.database_lock, menu); inflater.inflate(R.menu.database_lock, menu);
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass); MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
if (!entryContentsView.isPasswordPresent()) { if (entryContentsView != null && togglePassword != null) {
togglePassword.setVisible(false); if (!entryContentsView.isPasswordPresent()) {
} else { togglePassword.setVisible(false);
changeShowPasswordIcon(togglePassword); } else {
changeShowPasswordIcon(togglePassword);
}
} }
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url); MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
if (gotoUrl != null) {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes // In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set // so mEntry may not be set
if (mEntry == null) { if (mEntry == null) {
gotoUrl.setVisible(false); gotoUrl.setVisible(false);
} } else {
else { String url = mEntry.getUrl();
String url = mEntry.getUrl(); if (EmptyUtils.isNullOrEmpty(url)) {
if (EmptyUtils.isNullOrEmpty(url)) { // disable button if url is not available
// disable button if url is not available gotoUrl.setVisible(false);
gotoUrl.setVisible(false); }
} }
} }
return true; return true;
} }
@@ -385,9 +350,7 @@ public class EntryActivity extends LockingHideActivity {
return true; return true;
case R.id.menu_lock: case R.id.menu_lock:
App.setShutdown(); lockAndExit();
setResult(PasswordActivity.RESULT_EXIT_LOCK);
finish();
return true; return true;
case android.R.id.home : case android.R.id.home :
@@ -397,30 +360,6 @@ public class EntryActivity extends LockingHideActivity {
return super.onOptionsItemSelected(item); 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 @Override
public void finish() { public void finish() {
@@ -433,55 +372,4 @@ public class EntryActivity extends LockingHideActivity {
*/ */
super.finish(); 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();
}
} }

View File

@@ -28,20 +28,18 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV4; import com.keepassdroid.database.PwDate;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroup; import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupId; import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwIconStandard; 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.RunnableOnFinish;
import com.keepassdroid.database.edit.UpdateEntry; import com.keepassdroid.database.edit.UpdateEntry;
import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.fragments.GeneratePasswordDialogFragment; import com.keepassdroid.dialogs.GeneratePasswordDialogFragment;
import com.keepassdroid.fragments.IconPickerDialogFragment; import com.keepassdroid.dialogs.IconPickerDialogFragment;
import com.keepassdroid.icons.Icons; import com.keepassdroid.icons.Icons;
import com.keepassdroid.settings.PreferencesUtil; import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.tasks.ProgressTask; import com.keepassdroid.tasks.ProgressTask;
@@ -61,9 +59,6 @@ import com.keepassdroid.utils.Util;
import com.keepassdroid.view.EntryEditNewField; import com.keepassdroid.view.EntryEditNewField;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@@ -88,12 +83,12 @@ public class EntryEditActivity extends LockingHideActivity
// Views // Views
private ScrollView scrollView; private ScrollView scrollView;
private TextView entryTitleView; private EditText entryTitleView;
private TextView entryUserNameView; private EditText entryUserNameView;
private TextView entryUrlView; private EditText entryUrlView;
private TextView entryPasswordView; private EditText entryPasswordView;
private TextView entryConfirmationPasswordView; private EditText entryConfirmationPasswordView;
private TextView entryCommentView; private EditText entryCommentView;
private ViewGroup entryExtraFieldsContainer; private ViewGroup entryExtraFieldsContainer;
/** /**
@@ -102,9 +97,11 @@ public class EntryEditActivity extends LockingHideActivity
* @param pw Entry to update * @param pw Entry to update
*/ */
public static void Launch(Activity act, PwEntry pw) { public static void Launch(Activity act, PwEntry pw) {
Intent intent = new Intent(act, EntryEditActivity.class); if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID())); Intent intent = new Intent(act, EntryEditActivity.class);
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE); 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 * @param pwGroup Group who will contains new entry
*/ */
public static void Launch(Activity act, PwGroup pwGroup) { public static void Launch(Activity act, PwGroup pwGroup) {
Intent intent = new Intent(act, EntryEditActivity.class); if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
intent.putExtra(KEY_PARENT, pwGroup.getId()); Intent intent = new Intent(act, EntryEditActivity.class);
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE); intent.putExtra(KEY_PARENT, pwGroup.getId());
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
} }
@Override @Override
@@ -123,23 +122,23 @@ public class EntryEditActivity extends LockingHideActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.entry_edit); 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)); toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
scrollView = (ScrollView) findViewById(R.id.entry_scroll); scrollView = findViewById(R.id.entry_scroll);
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
entryTitleView = (TextView) findViewById(R.id.entry_title); entryTitleView = findViewById(R.id.entry_title);
entryUserNameView = (TextView) findViewById(R.id.entry_user_name); entryUserNameView = findViewById(R.id.entry_user_name);
entryUrlView = (TextView) findViewById(R.id.entry_url); entryUrlView = findViewById(R.id.entry_url);
entryPasswordView = (TextView) findViewById(R.id.entry_password); entryPasswordView = findViewById(R.id.entry_password);
entryConfirmationPasswordView = (TextView) findViewById(R.id.entry_confpassword); entryConfirmationPasswordView = findViewById(R.id.entry_confpassword);
entryCommentView = (TextView) findViewById(R.id.entry_comment); entryCommentView = findViewById(R.id.entry_comment);
entryExtraFieldsContainer = (ViewGroup) findViewById(R.id.advanced_container); entryExtraFieldsContainer = findViewById(R.id.advanced_container);
// Likely the app has been killed exit the activity // Likely the app has been killed exit the activity
Database db = App.getDB(); Database db = App.getDB();
@@ -165,67 +164,47 @@ public class EntryEditActivity extends LockingHideActivity
} }
View iconButton = findViewById(R.id.icon_button); View iconButton = findViewById(R.id.icon_button);
iconButton.setOnClickListener(new View.OnClickListener() { iconButton.setOnClickListener(v ->
IconPickerDialogFragment.launch(EntryEditActivity.this));
public void onClick(View v) {
IconPickerDialogFragment.launch(EntryEditActivity.this);
}
});
// Generate password button // Generate password button
View generatePassword = findViewById(R.id.generate_button); View generatePassword = findViewById(R.id.generate_button);
generatePassword.setOnClickListener(new OnClickListener() { generatePassword.setOnClickListener(v -> {
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
public void onClick(View v) { generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment(); });
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
}
});
// Save button // Save button
View save = findViewById(R.id.entry_save); 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) { OnFinish onFinish = new AfterSave();
if (!validateBeforeSaving()) { EntryEditActivity act = EntryEditActivity.this;
return; RunnableOnFinish task;
} if ( mIsNew ) {
mCallbackNewEntry = populateNewEntry(); task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
} else {
OnFinish onFinish = new AfterSave(); task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
EntryEditActivity act = EntryEditActivity.this; }
RunnableOnFinish task; ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
if ( mIsNew ) { pt.run();
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()) { if (mEntry.allowExtraFields()) {
View add = findViewById(R.id.add_new_field); View add = findViewById(R.id.add_new_field);
add.setVisibility(View.VISIBLE); 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 // Scroll bottom
public void onClick(View v) { scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
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);
}
});
}
}); });
} }
} }
@@ -262,21 +241,18 @@ public class EntryEditActivity extends LockingHideActivity
} }
protected PwEntry populateNewEntry() { 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; 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) if(mSelectedIconID != -1)
// or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID)); // or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID));
newEntry.setIcon(new PwIconStandard(mSelectedIconID)); newEntry.setIcon(new PwIconStandard(mSelectedIconID));
@@ -286,13 +262,13 @@ public class EntryEditActivity extends LockingHideActivity
} }
else { else {
// Keep previous icon, if no new one was selected // Keep previous icon, if no new one was selected
newEntry.setIcon(mEntry.icon); newEntry.setIcon(mEntry.getIconStandard());
} }
} }
newEntry.setUrl(entryUrlView.getText().toString(), db); newEntry.setUrl(entryUrlView.getText().toString());
newEntry.setUsername(entryUserNameView.getText().toString(), db); newEntry.setUsername(entryUserNameView.getText().toString());
newEntry.setNotes(entryCommentView.getText().toString(), db); newEntry.setNotes(entryCommentView.getText().toString());
newEntry.setPassword(entryPasswordView.getText().toString(), db); newEntry.setPassword(entryPasswordView.getText().toString());
if (newEntry.allowExtraFields()) { if (newEntry.allowExtraFields()) {
// Delete all new standard strings // Delete all new standard strings
@@ -307,6 +283,8 @@ public class EntryEditActivity extends LockingHideActivity
} }
} }
newEntry.endToDecodeReference(db);
return newEntry; return newEntry;
} }
@@ -334,11 +312,9 @@ public class EntryEditActivity extends LockingHideActivity
} }
protected void fillData() { 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()); App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
boolean visibilityFont = PreferencesUtil.fieldFontIsInVisibility(this);
entryTitleView.setText(mEntry.getTitle()); entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername()); entryUserNameView.setText(mEntry.getUsername());
entryUrlView.setText(mEntry.getUrl()); entryUrlView.setText(mEntry.getUrl());
@@ -346,14 +322,21 @@ public class EntryEditActivity extends LockingHideActivity
entryPasswordView.setText(password); entryPasswordView.setText(password);
entryConfirmationPasswordView.setText(password); entryConfirmationPasswordView.setText(password);
entryCommentView.setText(mEntry.getNotes()); 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()) { 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()) { for (Map.Entry<String, ProtectedString> pair : mEntry.getExtraProtectedFields().entrySet()) {
EntryEditNewField entryEditNewField = new EntryEditNewField(EntryEditActivity.this); EntryEditNewField entryEditNewField = new EntryEditNewField(EntryEditActivity.this);
entryEditNewField.setData(pair.getKey(), pair.getValue()); entryEditNewField.setData(pair.getKey(), pair.getValue());
entryEditNewField.setFontVisibility(visibilityFont); entryEditNewField.setFontVisibility(visibilityFontActivated);
container.addView(entryEditNewField); container.addView(entryEditNewField);
} }
} }
@@ -362,7 +345,7 @@ public class EntryEditActivity extends LockingHideActivity
@Override @Override
public void iconPicked(Bundle bundle) { public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID); 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)); currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
} }
@@ -382,12 +365,13 @@ public class EntryEditActivity extends LockingHideActivity
public void finish() { public void finish() {
// Assign entry callback as a result in all case // Assign entry callback as a result in all case
if (mCallbackNewEntry != null) { if (mCallbackNewEntry != null) {
Bundle bundle = new Bundle();
Intent intentEntry = new Intent(); Intent intentEntry = new Intent();
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
intentEntry.putExtras(bundle);
if (mIsNew) { if (mIsNew) {
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
setResult(ADD_ENTRY_RESULT_CODE, intentEntry); setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
} else { } else {
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry); setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
} }
} }

View File

@@ -39,7 +39,6 @@ import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import com.keepassdroid.adapters.NodeAdapter; 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.AddGroup;
import com.keepassdroid.database.edit.DeleteEntry; import com.keepassdroid.database.edit.DeleteEntry;
import com.keepassdroid.database.edit.DeleteGroup; import com.keepassdroid.database.edit.DeleteGroup;
import com.keepassdroid.dialog.ReadOnlyDialog; import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment; import com.keepassdroid.dialogs.GroupEditDialogFragment;
import com.keepassdroid.fragments.GroupEditDialogFragment; import com.keepassdroid.dialogs.IconPickerDialogFragment;
import com.keepassdroid.fragments.IconPickerDialogFragment; import com.keepassdroid.dialogs.ReadOnlyDialog;
import com.keepassdroid.password.PasswordActivity;
import com.keepassdroid.search.SearchResultsActivity; import com.keepassdroid.search.SearchResultsActivity;
import com.keepassdroid.tasks.ProgressTask; import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.view.ListNodesWithAddButtonView; import com.keepassdroid.view.AddNodeButtonView;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
public class GroupActivity extends ListNodesActivity public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener { implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private AddNodeButtonView addNodeButtonView;
protected boolean addGroupEnabled = false; protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false; protected boolean addEntryEnabled = false;
protected boolean isRoot = false; protected boolean isRoot = false;
protected boolean readOnly = false; protected boolean readOnly = false;
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE; protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
private ListNodesWithAddButtonView rootView;
private AutofillHelper autofillHelper; private AutofillHelper autofillHelper;
@@ -83,31 +84,41 @@ public class GroupActivity extends ListNodesActivity
private static final String TAG = "Group Activity:"; private static final String TAG = "Group Activity:";
public static void launch(Activity act) { 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) { public static void launch(Activity act, PwGroup group) {
Intent intent = new Intent(act, GroupActivity.class); if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
if ( group != null ) { Intent intent = new Intent(act, GroupActivity.class);
intent.putExtra(KEY_ENTRY, group.getId()); if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
act.startActivityForResult(intent, 0);
} }
act.startActivityForResult(intent, 0);
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, AssistStructure assistStructure) { 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) @RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) { public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) {
if ( assistStructure != null ) { if ( assistStructure != null ) {
Intent intent = new Intent(act, GroupActivity.class); if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
if ( group != null ) { Intent intent = new Intent(act, GroupActivity.class);
intent.putExtra(KEY_ENTRY, group.getId()); 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 { } else {
launch(act, group); launch(act, group);
} }
@@ -124,31 +135,30 @@ public class GroupActivity extends ListNodesActivity
} }
// Construct main view // Construct main view
rootView = new ListNodesWithAddButtonView(this); setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
rootView.enableAddGroup(addGroupEnabled);
rootView.enableAddEntry(addEntryEnabled);
setContentView(rootView);
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(""); toolbar.setTitle("");
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
if ( mCurrentGroup.getParent() != null ) if ( mCurrentGroup.getParent() != null )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp); toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
rootView.setAddGroupClickListener(new View.OnClickListener() { addNodeButtonView.setAddGroupClickListener(v -> {
public void onClick(View v) { editGroupDialogAction = EditGroupDialogAction.CREATION;
editGroupDialogAction = EditGroupDialogAction.CREATION; GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment(); groupEditDialogFragment.show(getSupportFragmentManager(),
groupEditDialogFragment.show(getSupportFragmentManager(), GroupEditDialogFragment.TAG_CREATE_GROUP);
GroupEditDialogFragment.TAG_CREATE_GROUP);
}
});
rootView.setAddEntryClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup);
}
}); });
addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup));
setGroupTitle(); setGroupTitle();
setGroupIcon(); setGroupIcon();
@@ -172,19 +182,20 @@ public class GroupActivity extends ListNodesActivity
PwGroup root = db.pm.rootGroup; PwGroup root = db.pm.rootGroup;
Log.w(TAG, "Creating tree view"); Log.w(TAG, "Creating tree view");
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(KEY_ENTRY); PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
if ( pwGroupId == null ) { if ( pwGroupId == null ) {
currentGroup = root; currentGroup = root;
} else { } else {
currentGroup = db.pm.groups.get(pwGroupId); currentGroup = db.pm.groups.get(pwGroupId);
} }
addGroupEnabled = !readOnly; if (currentGroup != null) {
addEntryEnabled = !readOnly; addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly; // TODO ReadOnly
isRoot = (currentGroup == root); isRoot = (currentGroup == root);
if ( !currentGroup.allowAddEntryIfIsRoot() ) if (!currentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled; addEntryEnabled = !isRoot && addEntryEnabled;
}
return currentGroup; return currentGroup;
} }
@@ -276,7 +287,14 @@ public class GroupActivity extends ListNodesActivity
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// Show button on resume // Show button on resume
rootView.showButton(); addNodeButtonView.showButton();
}
@Override
protected void onStop() {
super.onStop();
// Hide button
addNodeButtonView.hideButton();
} }
@Override @Override
@@ -291,9 +309,8 @@ public class GroupActivity extends ListNodesActivity
@Override @Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) { public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom); super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
// Show button if hide after sort // Show button if hide after sort
rootView.showButton(); addNodeButtonView.showButton();
} }
protected void setGroupIcon() { protected void setGroupIcon() {
@@ -339,6 +356,7 @@ public class GroupActivity extends ListNodesActivity
searchView = (SearchView) searchItem.getActionView(); searchView = (SearchView) searchItem.getActionView();
} }
if (searchView != null) { if (searchView != null) {
// TODO Flickering when locking, will be better with content provider
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class))); searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
} }
@@ -359,9 +377,7 @@ public class GroupActivity extends ListNodesActivity
return true; return true;
case R.id.menu_lock: case R.id.menu_lock:
App.setShutdown(); lockAndExit();
setResult(PasswordActivity.RESULT_EXIT_LOCK);
finish();
return true; return true;
case R.id.menu_change_master_key: case R.id.menu_change_master_key:

View File

@@ -38,7 +38,6 @@ import android.widget.TextView;
import com.keepassdroid.adapters.NodeAdapter; import com.keepassdroid.adapters.NodeAdapter;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.compat.ActivityCompat;
import com.keepassdroid.compat.EditorCompat; import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
@@ -46,13 +45,13 @@ import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwNode; import com.keepassdroid.database.PwNode;
import com.keepassdroid.database.edit.AfterAddNodeOnFinish; import com.keepassdroid.database.edit.AfterAddNodeOnFinish;
import com.keepassdroid.database.edit.OnFinish; import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment; import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.SortDialogFragment; import com.keepassdroid.dialogs.SortDialogFragment;
import com.keepassdroid.settings.PreferencesUtil; import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.tasks.UIToastTask; import com.keepassdroid.tasks.UIToastTask;
import com.keepassdroid.utils.MenuUtil; import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.database.SortNodeEnum; import com.keepassdroid.database.SortNodeEnum;
import com.keepassdroid.view.AssignPasswordHelper; import com.keepassdroid.password.AssignPasswordHelper;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
public abstract class ListNodesActivity extends LockingActivity public abstract class ListNodesActivity extends LockingActivity
@@ -60,8 +59,6 @@ public abstract class ListNodesActivity extends LockingActivity
NodeAdapter.OnNodeClickCallback, NodeAdapter.OnNodeClickCallback,
SortDialogFragment.SortSelectionListener { SortDialogFragment.SortSelectionListener {
public static final String KEY_ENTRY = "entry";
protected PwGroup mCurrentGroup; protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter; protected NodeAdapter mAdapter;
@@ -83,8 +80,9 @@ public abstract class ListNodesActivity extends LockingActivity
prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs = PreferenceManager.getDefaultSharedPreferences(this);
ActivityCompat.invalidateOptionsMenu(this); invalidateOptionsMenu();
// TODO Move in search
setContentView(R.layout.list_nodes); setContentView(R.layout.list_nodes);
mCurrentGroup = initCurrentGroup(); mCurrentGroup = initCurrentGroup();
@@ -202,7 +200,8 @@ public abstract class ListNodesActivity extends LockingActivity
return true; return true;
default: default:
MenuUtil.onDefaultMenuOptionsItemSelected(this, item); // Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@@ -262,6 +261,7 @@ public abstract class ListNodesActivity extends LockingActivity
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// TODO BUG HERE
super.startActivityForResult(intent, requestCode, options); super.startActivityForResult(intent, requestCode, options);
} }
} }
@@ -299,6 +299,7 @@ public abstract class ListNodesActivity extends LockingActivity
PwGroup recycleBin = database.getRecycleBin(); PwGroup recycleBin = database.getRecycleBin();
// Add trash if it doesn't exists // Add trash if it doesn't exists
if (parent.equals(recycleBin) if (parent.equals(recycleBin)
&& mCurrentGroup != null
&& mCurrentGroup.getParent() == null && mCurrentGroup.getParent() == null
&& !mCurrentGroup.equals(recycleBin)) { && !mCurrentGroup.equals(recycleBin)) {
mAdapter.addNode(parent); mAdapter.addNode(parent);

View File

@@ -19,46 +19,95 @@
*/ */
package com.keepassdroid.activities; package com.keepassdroid.activities;
import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.settings.PreferencesUtil; import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.stylish.StylishActivity; import com.keepassdroid.stylish.StylishActivity;
import com.keepassdroid.timeout.TimeoutHelper; import com.keepassdroid.timeout.TimeoutHelper;
public abstract class LockingActivity extends StylishActivity { 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 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 @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this)) { if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this)) {
screenReceiver = new ScreenReceiver(); screenReceiver = new ScreenReceiver();
registerReceiver(screenReceiver, new IntentFilter((Intent.ACTION_SCREEN_OFF))); registerReceiver(screenReceiver, new IntentFilter((Intent.ACTION_SCREEN_OFF)));
} else } else
screenReceiver = null; 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 @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
TimeoutHelper.checkShutdown(this); // After the first creation
TimeoutHelper.recordTime(this); // 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 @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
// If the time is out during our navigation in activity -> close the Activity
TimeoutHelper.checkTime(this); TimeoutHelper.checkTime(this);
TimeoutHelper.checkShutdown(this);
} }
@Override @Override
@@ -68,6 +117,12 @@ public abstract class LockingActivity extends StylishActivity {
unregisterReceiver(screenReceiver); unregisterReceiver(screenReceiver);
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AT_LEAST_SECOND_SHOWN_KEY, true);
}
public class ScreenReceiver extends BroadcastReceiver { public class ScreenReceiver extends BroadcastReceiver {
@Override @Override
@@ -77,7 +132,7 @@ public abstract class LockingActivity extends StylishActivity {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) { if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) {
App.setShutdown(); App.setShutdown();
TimeoutHelper.checkShutdown(LockingActivity.this); checkShutdown(LockingActivity.this);
} }
} }
} }

View File

@@ -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
}
}
}
}

View 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();
}
}

View File

@@ -28,7 +28,7 @@ import java.util.Set;
import com.keepassdroid.database.security.ProtectedBinary; import com.keepassdroid.database.security.ProtectedBinary;
public class BinaryPool { public class BinaryPool {
private HashMap<Integer, ProtectedBinary> pool = new HashMap<Integer, ProtectedBinary>(); private HashMap<Integer, ProtectedBinary> pool = new HashMap<>();
public BinaryPool() { public BinaryPool() {
@@ -63,12 +63,10 @@ public class BinaryPool {
@Override @Override
public boolean operate(PwEntryV4 entry) { public boolean operate(PwEntryV4 entry) {
for (PwEntryV4 histEntry : entry.history) { for (PwEntryV4 histEntry : entry.getHistory()) {
poolAdd(histEntry.binaries); poolAdd(histEntry.getBinaries());
} }
poolAdd(entry.getBinaries());
poolAdd(entry.binaries);
return true; return true;
} }

View File

@@ -52,7 +52,7 @@ public abstract class EntrySearchHandler extends EntryHandler<PwEntry> {
return true; return true;
} }
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) { if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
return true; return true;
} }

View File

@@ -39,7 +39,7 @@ public class EntrySearchHandlerAll extends EntryHandler<PwEntry> {
return true; return true;
} }
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) { if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
return true; return true;
} }

View File

@@ -37,7 +37,7 @@ public class EntrySearchHandlerV4 extends EntrySearchHandler {
protected boolean searchID(PwEntry e) { protected boolean searchID(PwEntry e) {
PwEntryV4 entry = (PwEntryV4) e; PwEntryV4 entry = (PwEntryV4) e;
if (sp.searchInUUIDs) { if (sp.searchInUUIDs) {
String hex = UuidUtil.toHexString(entry.uuid); String hex = UuidUtil.toHexString(entry.getUUID());
return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0; return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0;
} }

View File

@@ -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);
}

View File

@@ -19,28 +19,12 @@
*/ */
package com.keepassdroid.database; 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(); long getUsageCount();
void setUsageCount(long count); void setUsageCount(long count);
Date getLocationChanged(); PwDate getLocationChanged();
void setLocationChanged(Date date); void setLocationChanged(PwDate date);
} }

View File

@@ -41,13 +41,14 @@ import com.keepassdroid.utils.Util;
public abstract class PwDatabase { public abstract class PwDatabase {
public static final UUID UUID_ZERO = new UUID(0,0);
public byte masterKey[] = new byte[32]; public byte masterKey[] = new byte[32];
public byte[] finalKey; public byte[] finalKey;
public String name = "KeePass database"; public String name = "KeePass database";
public PwGroup rootGroup; public PwGroup rootGroup;
public PwIconFactory iconFactory = new PwIconFactory(); public PwIconFactory iconFactory = new PwIconFactory();
public Map<PwGroupId, PwGroup> groups = new HashMap<PwGroupId, PwGroup>(); public Map<PwGroupId, PwGroup> groups = new HashMap<>();
public Map<UUID, PwEntry> entries = new HashMap<UUID, PwEntry>(); public Map<UUID, PwEntry> entries = new HashMap<>();
private static boolean isKDBExtension(String filename) { private static boolean isKDBExtension(String filename) {
@@ -317,8 +318,8 @@ public abstract class PwDatabase {
public void populateGlobals(PwGroup currentGroup) { public void populateGlobals(PwGroup currentGroup) {
List<PwGroup> childGroups = currentGroup.childGroups; List<PwGroup> childGroups = currentGroup.getChildGroups();
List<PwEntry> childEntries = currentGroup.childEntries; List<PwEntry> childEntries = currentGroup.getChildEntries();
for (int i = 0; i < childEntries.size(); i++ ) { for (int i = 0; i < childEntries.size(); i++ ) {
PwEntry cur = childEntries.get(i); PwEntry cur = childEntries.get(i);

View File

@@ -69,9 +69,9 @@ public class PwDatabaseV3 extends PwDatabase {
public PwEntry metaInfo; public PwEntry metaInfo;
// all entries // all entries
public List<PwEntry> entries = new ArrayList<PwEntry>(); public List<PwEntry> entries = new ArrayList<>();
// all groups // all groups
public List<PwGroup> groups = new ArrayList<PwGroup>(); public List<PwGroup> groups = new ArrayList<>();
// Algorithm used to encrypt the database // Algorithm used to encrypt the database
public PwEncryptionAlgorithm algorithm; public PwEncryptionAlgorithm algorithm;
public int numKeyEncRounds; public int numKeyEncRounds;
@@ -105,7 +105,7 @@ public class PwDatabaseV3 extends PwDatabase {
List<PwGroup> kids = new ArrayList<PwGroup>(); List<PwGroup> kids = new ArrayList<PwGroup>();
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) groups.get(i); PwGroupV3 grp = (PwGroupV3) groups.get(i);
if (grp.level == target) if (grp.getLevel() == target)
kids.add(grp); kids.add(grp);
} }
return kids; return kids;
@@ -114,8 +114,8 @@ public class PwDatabaseV3 extends PwDatabase {
public int getRootGroupId() { public int getRootGroupId() {
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) groups.get(i); PwGroupV3 grp = (PwGroupV3) groups.get(i);
if (grp.level == 0) { if (grp.getLevel() == 0) {
return grp.groupId; return grp.getGroupId();
} }
} }
@@ -124,13 +124,13 @@ public class PwDatabaseV3 extends PwDatabase {
public List<PwGroup> getGrpChildren(PwGroupV3 parent) { public List<PwGroup> getGrpChildren(PwGroupV3 parent) {
int idx = groups.indexOf(parent); int idx = groups.indexOf(parent);
int target = parent.level + 1; int target = parent.getLevel() + 1;
List<PwGroup> kids = new ArrayList<PwGroup>(); List<PwGroup> kids = new ArrayList<PwGroup>();
while (++idx < groups.size()) { while (++idx < groups.size()) {
PwGroupV3 grp = (PwGroupV3) groups.get(idx); PwGroupV3 grp = (PwGroupV3) groups.get(idx);
if (grp.level < target) if (grp.getLevel() < target)
break; break;
else if (grp.level == target) else if (grp.getLevel() == target)
kids.add(grp); kids.add(grp);
} }
return kids; return kids;
@@ -145,7 +145,7 @@ public class PwDatabaseV3 extends PwDatabase {
*/ */
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
PwEntryV3 ent = (PwEntryV3) entries.get(i); PwEntryV3 ent = (PwEntryV3) entries.get(i);
if (ent.groupId == parent.groupId) if (ent.getGroupId() == parent.getGroupId())
kids.add(ent); kids.add(ent);
} }
return kids; return kids;
@@ -163,11 +163,11 @@ public class PwDatabaseV3 extends PwDatabase {
List<PwGroup> rootChildGroups = getGrpRoots(); List<PwGroup> rootChildGroups = getGrpRoots();
root.setGroups(rootChildGroups); root.setGroups(rootChildGroups);
root.childEntries = new ArrayList<>(); root.setEntries(new ArrayList<>());
root.level = -1; root.setLevel(-1);
for (int i = 0; i < rootChildGroups.size(); i++) { for (int i = 0; i < rootChildGroups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i); PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i);
grp.parent = root; grp.setParent(root);
constructTree(grp); constructTree(grp);
} }
return; return;
@@ -176,18 +176,18 @@ public class PwDatabaseV3 extends PwDatabase {
// I'm in non-root // I'm in non-root
// get child groups // get child groups
currentGroup.setGroups(getGrpChildren(currentGroup)); currentGroup.setGroups(getGrpChildren(currentGroup));
currentGroup.childEntries = getEntries(currentGroup); currentGroup.setEntries(getEntries(currentGroup));
// set parent in child entries // set parent in child entries
for (int i = 0; i < currentGroup.numbersOfChildEntries(); i++) { for (int i = 0; i < currentGroup.numbersOfChildEntries(); i++) {
PwEntryV3 entry = (PwEntryV3) currentGroup.childEntries.get(i); PwEntryV3 entry = (PwEntryV3) currentGroup.getChildEntryAt(i);
entry.parent = currentGroup; entry.setParent(currentGroup);
} }
// recursively construct child groups // recursively construct child groups
for (int i = 0; i < currentGroup.numbersOfChildGroups(); i++) { for (int i = 0; i < currentGroup.numbersOfChildGroups(); i++) {
PwGroupV3 grp = (PwGroupV3) currentGroup.childGroups.get(i); PwGroupV3 grp = (PwGroupV3) currentGroup.getChildGroupAt(i);
grp.parent = currentGroup; grp.setParent(currentGroup);
constructTree((PwGroupV3) currentGroup.childGroups.get(i)); constructTree((PwGroupV3) currentGroup.getChildGroupAt(i));
} }
return; return;
} }
@@ -316,11 +316,11 @@ public class PwDatabaseV3 extends PwDatabase {
public boolean isBackup(PwGroup group) { public boolean isBackup(PwGroup group) {
PwGroupV3 g = (PwGroupV3) group; PwGroupV3 g = (PwGroupV3) group;
while (g != null) { while (g != null) {
if (g.level == 0 && g.name.equalsIgnoreCase("Backup")) { if (g.getLevel() == 0 && g.getName().equalsIgnoreCase("Backup")) {
return true; return true;
} }
g = g.parent; g = (PwGroupV3) g.getParent();
} }
return false; return false;
@@ -338,7 +338,7 @@ public class PwDatabaseV3 extends PwDatabase {
private void initAndAddGroup(String name, int iconId, PwGroup parent) { private void initAndAddGroup(String name, int iconId, PwGroup parent) {
PwGroup group = createGroup(); PwGroup group = createGroup();
group.initNewGroup(name, newGroupId()); group.initNewGroup(name, newGroupId());
group.icon = iconFactory.getIcon(iconId); group.setIcon(iconFactory.getIcon(iconId));
addGroupTo(group, parent); addGroupTo(group, parent);
} }

View File

@@ -58,8 +58,6 @@ import biz.source_code.base64Coder.Base64Coder;
public class PwDatabaseV4 extends PwDatabase { 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; public static final int DEFAULT_ROUNDS = 6000;
private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited
private static final long DEFAULT_HISTORY_MAX_SIZE = 6 * 1024 * 1024; // -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; public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
// TODO: Refactor me away to get directly from kdfParameters // TODO: Refactor me away to get directly from kdfParameters
public long numKeyEncRounds = 6000; public long numKeyEncRounds = 6000;
public Date nameChanged = DEFAULT_NOW; public Date nameChanged = new Date();
public Date settingsChanged = DEFAULT_NOW; public Date settingsChanged = new Date();
public String description = ""; public String description = "";
public Date descriptionChanged = DEFAULT_NOW; public Date descriptionChanged = new Date();
public String defaultUserName = ""; 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 keyChangeRecDays = -1;
public long keyChangeForceDays = 1; public long keyChangeForceDays = 1;
public boolean keyChangeForceOnce = false; public boolean keyChangeForceOnce = false;
@@ -87,17 +85,17 @@ public class PwDatabaseV4 extends PwDatabase {
public String color = ""; public String color = "";
public boolean recycleBinEnabled = true; public boolean recycleBinEnabled = true;
public UUID recycleBinUUID = UUID_ZERO; public UUID recycleBinUUID = UUID_ZERO;
public Date recycleBinChanged = DEFAULT_NOW; public Date recycleBinChanged = new Date();
public UUID entryTemplatesGroup = UUID_ZERO; public UUID entryTemplatesGroup = UUID_ZERO;
public Date entryTemplatesGroupChanged = DEFAULT_NOW; public Date entryTemplatesGroupChanged = new Date();
public int historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS; public int historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS;
public long historyMaxSize = DEFAULT_HISTORY_MAX_SIZE; public long historyMaxSize = DEFAULT_HISTORY_MAX_SIZE;
public UUID lastSelectedGroup = UUID_ZERO; public UUID lastSelectedGroup = UUID_ZERO;
public UUID lastTopVisibleGroup = UUID_ZERO; public UUID lastTopVisibleGroup = UUID_ZERO;
public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig(); public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig();
public List<PwDeletedObject> deletedObjects = new ArrayList<PwDeletedObject>(); public List<PwDeletedObject> deletedObjects = new ArrayList<>();
public List<PwIconCustom> customIcons = new ArrayList<PwIconCustom>(); public List<PwIconCustom> customIcons = new ArrayList<>();
public Map<String, String> customData = new HashMap<String, String>(); public Map<String, String> customData = new HashMap<>();
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters(); public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
public VariantDictionary publicCustomData = new VariantDictionary(); public VariantDictionary publicCustomData = new VariantDictionary();
public BinaryPool binPool = new BinaryPool(); public BinaryPool binPool = new BinaryPool();
@@ -269,25 +267,42 @@ public class PwDatabaseV4 extends PwDatabase {
public List<PwGroup> getGroups() { public List<PwGroup> getGroups() {
List<PwGroup> list = new ArrayList<PwGroup>(); List<PwGroup> list = new ArrayList<PwGroup>();
PwGroupV4 root = (PwGroupV4) rootGroup; PwGroupV4 root = (PwGroupV4) rootGroup;
root.buildChildGroupsRecursive(list); buildChildGroupsRecursive(root, list);
return 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 @Override
public List<PwGroup> getGrpRoots() { public List<PwGroup> getGrpRoots() {
return rootGroup.childGroups; return rootGroup.getChildGroups();
} }
@Override @Override
public List<PwEntry> getEntries() { public List<PwEntry> getEntries() {
List<PwEntry> list = new ArrayList<PwEntry>(); List<PwEntry> list = new ArrayList<PwEntry>();
PwGroupV4 root = (PwGroupV4) rootGroup; PwGroupV4 root = (PwGroupV4) rootGroup;
root.buildChildEntriesRecursive(list); buildChildEntriesRecursive(root, list);
return 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 @Override
public long getNumRounds() { public long getNumRounds() {
return numKeyEncRounds; return numKeyEncRounds;
@@ -351,13 +366,13 @@ public class PwDatabaseV4 extends PwDatabase {
if (getRecycleBin() == null) { if (getRecycleBin() == null) {
// Create recycle bin // Create recycle bin
PwGroupV4 recycleBin = new PwGroupV4(true, true, RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN)); PwGroupV4 recycleBin = new PwGroupV4(RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN));
recycleBin.enableAutoType = false; recycleBin.setEnableAutoType(false);
recycleBin.enableSearching = false; recycleBin.setEnableSearching(false);
recycleBin.isExpanded = false; recycleBin.setExpanded(false);
addGroupTo(recycleBin, rootGroup); addGroupTo(recycleBin, rootGroup);
recycleBinUUID = recycleBin.uuid; recycleBinUUID = recycleBin.getUUID();
} }
} }
@@ -480,7 +495,7 @@ public class PwDatabaseV4 extends PwDatabase {
public void initNew(String dbPath) { public void initNew(String dbPath) {
String filename = URLUtil.guessFileName(dbPath, null, null); 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); groups.put(rootGroup.getId(), rootGroup);
} }
@@ -508,7 +523,7 @@ public class PwDatabaseV4 extends PwDatabase {
return true; return true;
} }
PwGroupV4 g4 = (PwGroupV4) group; PwGroupV4 g4 = (PwGroupV4) group;
if (g4.customData.size() > 0) { if (g4.containsCustomData()) {
hasCustomData = true; hasCustomData = true;
return false; return false;
} }
@@ -528,7 +543,7 @@ public class PwDatabaseV4 extends PwDatabase {
} }
PwEntryV4 e4 = (PwEntryV4)entry; PwEntryV4 e4 = (PwEntryV4)entry;
if (e4.customData.size() > 0) { if (e4.containsCustomData()) {
hasCustomData = true; hasCustomData = true;
return false; return false;
} }

View File

@@ -19,14 +19,14 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import com.keepassdroid.app.App;
import com.keepassdroid.utils.Types;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import com.keepassdroid.app.App;
import com.keepassdroid.utils.Types;
/** Converting from the C Date format to the Java data format is /** 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 * expensive when done for every record at once. I use this class to
* allow lazy conversions between the formats. * allow lazy conversions between the formats.
@@ -34,7 +34,7 @@ import com.keepassdroid.utils.Types;
* *
*/ */
public class PwDate implements Cloneable, Serializable { public class PwDate implements Cloneable, Serializable {
private static final int DATE_SIZE = 5; private static final int DATE_SIZE = 5;
private boolean cDateBuilt = false; private boolean cDateBuilt = false;
@@ -42,6 +42,56 @@ public class PwDate implements Cloneable, Serializable {
private Date jDate; private Date jDate;
private byte[] cDate; 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) { public PwDate(byte[] buf, int offset) {
cDate = new byte[DATE_SIZE]; cDate = new byte[DATE_SIZE];
@@ -59,12 +109,13 @@ public class PwDate implements Cloneable, Serializable {
jDateBuilt = true; jDateBuilt = true;
} }
private PwDate() { public PwDate() {
jDate = new Date();
jDateBuilt = true;
} }
@Override @Override
public Object clone() { public PwDate clone() {
PwDate copy = new PwDate(); PwDate copy = new PwDate();
if ( cDateBuilt ) { if ( cDateBuilt ) {
@@ -82,9 +133,7 @@ public class PwDate implements Cloneable, Serializable {
return copy; return copy;
} }
public Date getDate() {
public Date getJDate() {
if ( ! jDateBuilt ) { if ( ! jDateBuilt ) {
jDate = readTime(cDate, 0, App.getCalendar()); jDate = readTime(cDate, 0, App.getCalendar());
jDateBuilt = true; jDateBuilt = true;
@@ -102,7 +151,6 @@ public class PwDate implements Cloneable, Serializable {
return cDate; return cDate;
} }
/** /**
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
* to a java.util.Date instance. * to a java.util.Date instance.
@@ -189,7 +237,7 @@ public class PwDate implements Cloneable, Serializable {
} else if ( cDateBuilt && date.jDateBuilt ) { } else if ( cDateBuilt && date.jDateBuilt ) {
return Arrays.equals(date.getCDate(), cDate); return Arrays.equals(date.getCDate(), cDate);
} else { } else {
return IsSameDate(date.getJDate(), jDate); return IsSameDate(date.getDate(), jDate);
} }
} }

View File

@@ -22,18 +22,22 @@ package com.keepassdroid.database;
import com.keepassdroid.database.iterator.EntrySearchStringIterator; import com.keepassdroid.database.iterator.EntrySearchStringIterator;
import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.database.security.ProtectedString;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public abstract class PwEntry extends PwNode implements Cloneable { public abstract class PwEntry extends PwNode implements Cloneable {
protected static final String PMS_TAN_ENTRY = "<TAN>"; private static final String PMS_TAN_ENTRY = "<TAN>";
public PwIconStandard icon = PwIconStandard.FIRST; protected UUID uuid = PwDatabase.UUID_ZERO;
@Override
protected void construct() {
super.construct();
uuid = UUID.randomUUID();
}
public static PwEntry getInstance(PwGroup parent) { public static PwEntry getInstance(PwGroup parent) {
if (parent instanceof PwGroupV3) { if (parent instanceof PwGroupV3) {
return new PwEntryV3((PwGroupV3)parent); return new PwEntryV3((PwGroupV3)parent);
@@ -45,23 +49,23 @@ public abstract class PwEntry extends PwNode implements Cloneable {
throw new RuntimeException("Unknown PwGroup instance."); throw new RuntimeException("Unknown PwGroup instance.");
} }
} }
@Override @Override
public Object clone() { public PwEntry clone() {
PwEntry newEntry; PwEntry newEntry;
try { try {
newEntry = (PwEntry) super.clone(); newEntry = (PwEntry) super.clone();
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
assert(false);
throw new RuntimeException("Clone should be supported"); throw new RuntimeException("Clone should be supported");
} }
return newEntry; return newEntry;
} }
public PwEntry clone(boolean deepStrings) { @Override
return (PwEntry) clone(); protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
} super.addCloneAttributesToNewEntry(newEntry);
// uuid is clone automatically (IMMUTABLE)
}
@Override @Override
public Type getType() { public Type getType() {
@@ -69,65 +73,41 @@ public abstract class PwEntry extends PwNode implements Cloneable {
} }
public void assign(PwEntry source) { public void assign(PwEntry source) {
icon = source.icon; super.assign(source);
} uuid = source.uuid;
public abstract UUID getUUID();
public abstract void setUUID(UUID u);
public String getTitle() {
return getTitle(false, null);
}
public String getUsername() {
return getUsername(false, null);
} }
public String getPassword() { public UUID getUUID() {
return getPassword(false, null); return uuid;
}
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 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); return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
} }
@Override
public String getDisplayTitle() { public String getDisplayTitle() {
if ( isTan() ) { if ( isTan() ) {
return PMS_TAN_ENTRY + " " + getUsername(); 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 * Retrieve extra fields to show, key is the label, value is the value of field
* @param pm Database
* @return Map of label/value * @return Map of label/value
*/ */
public Map<String, String> getExtraFields(PwDatabase pm) { public Map<String, String> getExtraFields() {
return new HashMap<>(); return new HashMap<>();
} }
@@ -163,6 +142,14 @@ public abstract class PwEntry extends PwNode implements Cloneable {
return new HashMap<>(); 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 * Add an extra field to the list
* @param label Label of field, must be unique * @param label Label of field, must be unique
@@ -183,13 +170,18 @@ public abstract class PwEntry extends PwNode implements Cloneable {
return false; return false;
} }
/**
* Create a backup of entry
*/
public void createBackup(PwDatabase db) {}
public EntrySearchStringIterator stringIterator() { public EntrySearchStringIterator stringIterator() {
return EntrySearchStringIterator.getInstance(this); return EntrySearchStringIterator.getInstance(this);
} }
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
Date now = new Date(); PwDate now = new PwDate();
setLastAccessTime(now); setLastAccessTime(now);
if (modified) { if (modified) {
@@ -222,69 +214,4 @@ public abstract class PwEntry extends PwNode implements Cloneable {
public int hashCode() { public int hashCode() {
return getUUID() != null ? getUUID().hashCode() : 0; 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;
}
}
} }

View File

@@ -42,13 +42,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.keepassdroid.database; package com.keepassdroid.database;
// PhoneID
import com.keepassdroid.utils.Types;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
@@ -71,137 +65,204 @@ import java.util.UUID;
* @author Naomaru Itoi <nao@phoneid.org> * @author Naomaru Itoi <nao@phoneid.org>
* @author Bill Zwicky <wrzwicky@pobox.com> * @author Bill Zwicky <wrzwicky@pobox.com>
* @author Dominik Reichl <dominik.reichl@t-online.de> * @author Dominik Reichl <dominik.reichl@t-online.de>
* @author Jeremy Jamet <jeremy.jamet@kunzisoft.com>
*/ */
public class PwEntryV3 extends PwEntry { 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. */ /** Size of byte buffer needed to hold this struct. */
public static final String PMS_ID_BINDESC = "bin-stream"; private static final String PMS_ID_BINDESC = "bin-stream";
public static final String PMS_ID_TITLE = "Meta-Info"; private static final String PMS_ID_TITLE = "Meta-Info";
public static final String PMS_ID_USER = "SYSTEM"; private static final String PMS_ID_USER = "SYSTEM";
public static final String PMS_ID_URL = "$"; private static final String PMS_ID_URL = "$";
// for tree traversing
private PwGroupV3 parent = null;
private int groupId;
private String title;
public int groupId; private String username;
public String username; private byte[] password;
private byte[] password; private String url;
private byte[] uuid; private String additional;
public String title;
public String url;
public String additional;
public PwDate tCreation;
public PwDate tLastMod;
public PwDate tLastAccess;
public PwDate tExpire;
/** A string describing what is in pBinaryData */ /** A string describing what is in pBinaryData */
public String binaryDesc; private String binaryDesc;
private byte[] binaryData; 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() { public PwEntryV3() {
super(); super();
} }
/*
public PwEntryV3(PwEntryV3 source) {
assign(source);
}
*/
public PwEntryV3(PwGroupV3 p) { public PwEntryV3(PwGroupV3 p) {
this(p, true, true); construct();
}
public PwEntryV3(PwGroupV3 p, boolean initId, boolean initDates) {
parent = p; parent = p;
groupId = ((PwGroupIdV3)parent.getId()).getId(); groupId = ((PwGroupIdV3)parent.getId()).getId(); // TODO remove
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);
}
} }
@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. * @return the actual password byte array.
*/ */
@Override @Override
public String getPassword(boolean decodeRef, PwDatabase db) { public String getPassword() {
if (password == null) { if (password == null) {
return ""; return "";
} }
return new String(password); return new String(password);
} }
@@ -209,7 +270,6 @@ public class PwEntryV3 extends PwEntry {
return password; return password;
} }
/** /**
* fill byte array * fill byte array
*/ */
@@ -230,10 +290,8 @@ public class PwEntryV3 extends PwEntry {
System.arraycopy( buf, offset, password, 0, len ); System.arraycopy( buf, offset, password, 0, len );
} }
@Override @Override
public void setPassword(String pass, PwDatabase db) { public void setPassword(String pass) {
byte[] password; byte[] password;
try { try {
password = pass.getBytes("UTF-8"); password = pass.getBytes("UTF-8");
@@ -252,8 +310,6 @@ public class PwEntryV3 extends PwEntry {
return binaryData; return binaryData;
} }
/** Securely erase old data before copying new. */ /** Securely erase old data before copying new. */
public void setBinaryData( byte[] buf, int offset, int len ) { public void setBinaryData( byte[] buf, int offset, int len ) {
if( binaryData != null ) { if( binaryData != null ) {
@@ -264,6 +320,14 @@ public class PwEntryV3 extends PwEntry {
System.arraycopy( buf, offset, binaryData, 0, len ); 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 // Determine if this is a MetaStream entry
@Override @Override
public boolean isMetaStream() { public boolean isMetaStream() {
@@ -280,249 +344,4 @@ public class PwEntryV3 extends PwEntry {
return true; 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;
}
} }

View File

@@ -21,293 +21,205 @@ package com.keepassdroid.database;
import com.keepassdroid.database.security.ProtectedBinary; import com.keepassdroid.database.security.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString; 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.ArrayList;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
public class PwEntryV4 extends PwEntry implements ITimeLogger { public class PwEntryV4 extends PwEntry implements ITimeLogger {
public static final String STR_TITLE = "Title"; public static final String STR_TITLE = "Title";
public static final String STR_USERNAME = "UserName"; public static final String STR_USERNAME = "UserName";
public static final String STR_PASSWORD = "Password"; public static final String STR_PASSWORD = "Password";
public static final String STR_URL = "URL"; public static final String STR_URL = "URL";
public static final String STR_NOTES = "Notes"; public static final String STR_NOTES = "Notes";
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 { // To decode each field not serializable
private static final long OBF_OPT_NONE = 0; private transient PwDatabase mDatabase = null;
private transient boolean mDecodeRef = false;
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();
}
}
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() { public PwEntryV4() {
super();
} }
public PwEntryV4(PwGroupV4 p) { public PwEntryV4(PwGroupV4 p) {
this(p, true, true); construct();
}
public PwEntryV4(PwGroupV4 p, boolean initId, boolean initDates) {
parent = p; 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 @Override
public PwEntry clone(boolean deepStrings) { public void startToDecodeReference(PwDatabase db) {
PwEntryV4 entry = (PwEntryV4) super.clone(deepStrings); this.mDatabase = db;
this.mDecodeRef = true;
if (deepStrings) {
entry.fields = (HashMap<String, ProtectedString>) fields.clone();
}
return entry;
} }
@Override @Override
public void assign(PwEntry source) { public void endToDecodeReference(PwDatabase db) {
this.mDatabase = null;
if ( ! (source instanceof PwEntryV4) ) { this.mDecodeRef = false;
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;
} }
@Override private String decodeRefKey(boolean decodeRef, String key) {
public Object clone() {
PwEntryV4 newEntry = (PwEntryV4) super.clone();
return newEntry;
}
private String decodeRefKey(boolean decodeRef, String key, PwDatabase db) {
String text = getString(key); String text = getString(key);
if (decodeRef) { if (decodeRef) {
text = decodeRef(text, db); text = decodeRef(text, mDatabase);
} }
return text; return text;
} }
private String decodeRef(String text, PwDatabase db) { private String decodeRef(String text, PwDatabase db) {
if (db == null) { return text; } if (db == null) { return text; }
SprEngineV4 spr = new SprEngineV4();
SprEngine spr = SprEngine.getInstance(db);
return spr.compile(text, this, db); return spr.compile(text, this, db);
} }
@Override @Override
public String getUsername(boolean decodeRef, PwDatabase db) { public String getUsername() {
return decodeRefKey(decodeRef, STR_USERNAME, db); return decodeRefKey(mDecodeRef, STR_USERNAME);
} }
@Override @Override
public String getTitle(boolean decodeRef, PwDatabase db) { public String getTitle() {
return decodeRefKey(decodeRef, STR_TITLE, db); return decodeRefKey(mDecodeRef, STR_TITLE);
} }
@Override @Override
public String getPassword(boolean decodeRef, PwDatabase db) { public String getPassword() {
return decodeRefKey(decodeRef, STR_PASSWORD, db); return decodeRefKey(mDecodeRef, STR_PASSWORD);
} }
@Override @Override
public Date getLastAccessTime() { public void setTitle(String title) {
return lastAccess; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
}
@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;
boolean protect = db.memoryProtection.protectTitle; boolean protect = db.memoryProtection.protectTitle;
setString(STR_TITLE, title, protect); setString(STR_TITLE, title, protect);
} }
@Override @Override
public void setUsername(String user, PwDatabase d) { public void setUsername(String user) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectUserName; boolean protect = db.memoryProtection.protectUserName;
setString(STR_USERNAME, user, protect); setString(STR_USERNAME, user, protect);
} }
@Override @Override
public void setPassword(String pass, PwDatabase d) { public void setPassword(String pass) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectPassword; boolean protect = db.memoryProtection.protectPassword;
setString(STR_PASSWORD, pass, protect); setString(STR_PASSWORD, pass, protect);
} }
@Override @Override
public void setUrl(String url, PwDatabase d) { public void setUrl(String url) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectUrl; boolean protect = db.memoryProtection.protectUrl;
setString(STR_URL, url, protect); setString(STR_URL, url, protect);
} }
@Override @Override
public void setNotes(String notes, PwDatabase d) { public void setNotes(String notes) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectNotes; boolean protect = db.memoryProtection.protectNotes;
setString(STR_NOTES, notes, protect); 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 @Override
public PwGroupV4 getParent() { public PwGroupV4 getParent() {
return parent; return parent;
} }
@Override @Override
public UUID getUUID() { public void setParent(PwGroup parent) {
return uuid; this.parent = (PwGroupV4) parent;
} }
@Override
public void setUUID(UUID u) {
uuid = u;
}
public String getString(String key) { public String getString(String key) {
ProtectedString value = fields.get(key); ProtectedString value = fields.get(key);
if ( value == null ) return new String(""); if ( value == null ) return "";
return value.toString(); return value.toString();
} }
@@ -317,7 +229,15 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
fields.put(key, ps); 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; return parentGroupLastMod;
} }
@@ -325,109 +245,32 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
return usageCount; return usageCount;
} }
public void setLocationChanged(Date date) { public void setLocationChanged(PwDate date) {
parentGroupLastMod = date; parentGroupLastMod = date;
} }
public void setUsageCount(long count) { public void setUsageCount(long count) {
usageCount = count; usageCount = count;
} }
@Override
public boolean expires() {
return expires;
}
public void setExpires(boolean exp) { @Override
expires = exp; public String getNotes() {
return decodeRefKey(mDecodeRef, STR_NOTES);
} }
@Override @Override
public String getNotes(boolean decodeRef, PwDatabase db) { public String getUrl() {
return decodeRefKey(decodeRef, STR_NOTES, db); return decodeRefKey(mDecodeRef, STR_URL);
}
@Override
public String getUrl(boolean decodeRef, PwDatabase db) {
return decodeRefKey(decodeRef, STR_URL, db);
} }
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) { if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; 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 @Override
public boolean allowExtraFields() { public boolean allowExtraFields() {
@@ -444,7 +287,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
if (fields.size() > 0) { if (fields.size() > 0) {
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) { for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
String key = pair.getKey(); String key = pair.getKey();
if (!PwEntryV4.IsStandardField(key)) { if (!PwEntryV4.isStandardField(key)) {
protectedFields.put(key, pair.getValue()); protectedFields.put(key, pair.getValue());
} }
} }
@@ -453,23 +296,23 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
} }
@Override @Override
public Map<String, String> getExtraFields(PwDatabase pm) { public Map<String, String> getExtraFields() {
Map<String, String> extraFields = super.getExtraFields(pm); Map<String, String> extraFields = super.getExtraFields();
SprEngine spr = SprEngine.getInstance(pm); SprEngineV4 spr = new SprEngineV4();
// Display custom fields // Display custom fields
if (fields.size() > 0) { if (fields.size() > 0) {
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) { for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
String key = pair.getKey(); String key = pair.getKey();
// TODO Add hidden style for protection field // TODO Add hidden style for protection field
if (!PwEntryV4.IsStandardField(key)) { if (!PwEntryV4.isStandardField(key)) {
extraFields.put(key, spr.compile(pair.getValue().toString(), this, pm)); extraFields.put(key, spr.compile(pair.getValue().toString(), this, mDatabase));
} }
} }
} }
return extraFields; return extraFields;
} }
public static boolean IsStandardField(String key) { private static boolean isStandardField(String key) {
return key.equals(STR_TITLE) || key.equals(STR_USERNAME) return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|| key.equals(STR_PASSWORD) || key.equals(STR_URL) || key.equals(STR_PASSWORD) || key.equals(STR_URL)
|| key.equals(STR_NOTES); || key.equals(STR_NOTES);
@@ -484,13 +327,93 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator(); Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Map.Entry<String, ProtectedString> pair = iter.next(); Map.Entry<String, ProtectedString> pair = iter.next();
if (!PwEntryV4.IsStandardField(pair.getKey())) { if (!PwEntryV4.isStandardField(pair.getKey())) {
iter.remove(); 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() { public long getSize() {
long size = FIXED_LENGTH_SIZE; long size = FIXED_LENGTH_SIZE;
@@ -520,6 +443,68 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
return size; 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 @Override
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
super.touch(modified, touchParents); super.touch(modified, touchParents);
@@ -529,19 +514,13 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
@Override @Override
public void touchLocation() { public void touchLocation() {
parentGroupLastMod = new Date(); parentGroupLastMod = new PwDate();
}
@Override
public void setParent(PwGroup parent) {
this.parent = (PwGroupV4) parent;
} }
public boolean isSearchingEnabled() { public boolean isSearchingEnabled() {
if (parent != null) { if (parent != null) {
return parent.isSearchEnabled(); return parent.isSearchEnabled();
} }
return PwGroupV4.DEFAULT_SEARCHING_ENABLED; return PwGroupV4.DEFAULT_SEARCHING_ENABLED;
} }
} }

View File

@@ -24,24 +24,36 @@ import com.keepassdroid.utils.StrUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.List; import java.util.List;
public abstract class PwGroup extends PwNode { public abstract class PwGroup extends PwNode {
// TODO Change dependency and make private protected String name = "";
public List<PwGroup> childGroups = new ArrayList<>();
public List<PwEntry> childEntries = new ArrayList<>();
public String name = "";
public PwIconStandard icon;
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) { public void initNewGroup(String nm, PwGroupId newId) {
setId(newId); setId(newId);
name = nm; 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) { public void addChildGroup(PwGroup group) {
this.childGroups.add(group); this.childGroups.add(group);
} }
@@ -50,6 +62,15 @@ public abstract class PwGroup extends PwNode {
this.childEntries.add(entry); 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) { public void removeChildGroup(PwGroup group) {
this.childGroups.remove(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 * @return List of direct children (one level below) as PwNode
*/ */
public List<PwNode> getDirectChildren() { public List<PwNode> getDirectChildren() {
children.clear(); List<PwNode> children = new ArrayList<>();
children.addAll(childGroups); children.addAll(childGroups);
for(PwEntry child : childEntries) { for(PwEntry child : childEntries) {
if (!child.isMetaStream()) if (!child.isMetaStream())
@@ -85,14 +106,6 @@ public abstract class PwGroup extends PwNode {
return children; 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) { public boolean isContainedIn(PwGroup container) {
PwGroup cur = this; PwGroup cur = this;
while (cur != null) { while (cur != null) {
@@ -112,24 +125,20 @@ public abstract class PwGroup extends PwNode {
return getName(); return getName();
} }
public abstract String getName(); public String getName() {
return name;
public abstract Date getLastMod(); }
public PwIcon getIcon() {
return icon;
}
public abstract void setLastAccessTime(Date date);
public abstract void setLastModificationTime(Date date); public void setName(String name) {
this.name = name;
}
public boolean allowAddEntryIfIsRoot() { public boolean allowAddEntryIfIsRoot() {
return false; return false;
} }
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
Date now = new Date(); PwDate now = new PwDate();
setLastAccessTime(now); setLastAccessTime(now);
if (modified) { if (modified) {
@@ -247,68 +256,4 @@ public abstract class PwGroup extends PwNode {
PwGroupId groupId = getId(); PwGroupId groupId = getId();
return groupId != null ? groupId.hashCode() : 0; 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;
}
}
} }

View File

@@ -32,7 +32,6 @@ public class PwGroupIdV3 extends PwGroupId {
if ( ! (compare instanceof PwGroupIdV3) ) { if ( ! (compare instanceof PwGroupIdV3) ) {
return false; return false;
} }
PwGroupIdV3 cmp = (PwGroupIdV3) compare; PwGroupIdV3 cmp = (PwGroupIdV3) compare;
return id == cmp.id; return id == cmp.id;
} }
@@ -46,6 +45,4 @@ public class PwGroupIdV3 extends PwGroupId {
public int getId() { public int getId() {
return id; return id;
} }
} }

View File

@@ -14,9 +14,7 @@ public class PwGroupIdV4 extends PwGroupId {
if ( ! (id instanceof PwGroupIdV4) ) { if ( ! (id instanceof PwGroupIdV4) ) {
return false; return false;
} }
PwGroupIdV4 v4 = (PwGroupIdV4) id; PwGroupIdV4 v4 = (PwGroupIdV4) id;
return uuid.equals(v4.uuid); return uuid.equals(v4.uuid);
} }
@@ -28,5 +26,4 @@ public class PwGroupIdV4 extends PwGroupId {
public UUID getId() { public UUID getId() {
return uuid; return uuid;
} }
} }

View File

@@ -1,15 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * 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. * This file is part of KeePass DX.
* *
@@ -30,15 +20,6 @@ Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
package com.keepassdroid.database; 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 Brian Pellin <bpellin@gmail.com>
* @author Naomaru Itoi <nao@phoneid.org> * @author Naomaru Itoi <nao@phoneid.org>
@@ -47,39 +28,46 @@ import java.util.List;
*/ */
public class PwGroupV3 extends PwGroup { 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 // for tree traversing
public PwGroupV3 parent = null; private PwGroupV3 parent = null;
private int groupId;
public int groupId; private int level = 0; // short
public PwDate tCreation;
public PwDate tLastMod;
public PwDate tLastAccess;
public PwDate tExpire;
public int level; // short
/** Used by KeePass internally, don't use */ /** Used by KeePass internally, don't use */
public int flags; private int flags;
public void setGroups(List<PwGroup> groups) { public PwGroupV3() {
childGroups = groups; super();
} }
@Override @Override
public PwGroup getParent() { public PwGroup getParent() {
return parent; 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 @Override
public PwGroupId getId() { public PwGroupId getId() {
return new PwGroupIdV3(groupId); return new PwGroupIdV3(groupId);
@@ -91,76 +79,27 @@ public class PwGroupV3 extends PwGroup {
groupId = id3.getId(); groupId = id3.getId();
} }
@Override public int getFlags() {
public String getName() { return flags;
return name; }
}
public void setFlags(int flags) {
this.flags = flags;
}
@Override @Override
public Date getLastMod() { public String toString() {
return tLastMod.getJDate(); return getName();
} }
@Override public void populateBlankFields(PwDatabaseV3 db) {
public void setParent(PwGroup prt) { // TODO populate blanck field
parent = (PwGroupV3) prt; if (icon == null) {
level = parent.level + 1; icon = db.iconFactory.getIcon(1);
}
}
@Override if (name == null) {
public void initNewGroup(String nm, PwGroupId newId) { name = "";
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();
}
} }

View File

@@ -19,46 +19,37 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class PwGroupV4 extends PwGroup implements ITimeLogger { public class PwGroupV4 extends PwGroup implements ITimeLogger {
//public static final int FOLDER_ICON = 48;
public static final boolean DEFAULT_SEARCHING_ENABLED = true; 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) { public PwGroupV4(String name, PwIconStandard icon) {
if (createUUID) { super.construct();
uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
}
if (setTimes) {
creation = lastMod = lastAccess = new Date();
}
this.name = name; this.name = name;
this.icon = icon; this.icon = icon;
} }
@@ -66,65 +57,46 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
@Override @Override
public void initNewGroup(String nm, PwGroupId newId) { public void initNewGroup(String nm, PwGroupId newId) {
super.initNewGroup(nm, newId); super.initNewGroup(nm, newId);
parentGroupLastMod = new PwDate();
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
} }
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"); if ( subGroup == null ) throw new RuntimeException("subGroup");
childGroups.add(subGroup); childGroups.add(subGroup);
subGroup.parent = this;
if ( takeOwnership ) subGroup.parent = this; }
if ( updateLocationChanged ) subGroup.parentGroupLastMod = new Date(System.currentTimeMillis());
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership) { public void AddEntry(PwEntryV4 pe) {
AddEntry(pe, takeOwnership, false);
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
assert(pe != null); assert(pe != null);
addChildEntry(pe); addChildEntry(pe);
pe.setParent(this);
if ( takeOwnership ) pe.parent = this; }
if ( updateLocationChanged ) pe.setLocationChanged(new Date(System.currentTimeMillis()));
}
@Override @Override
public PwGroup getParent() { public PwGroup getParent() {
return parent; 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) { @Override
for ( int i = 0; i < numbersOfChildEntries(); i++ ) { public void setParent(PwGroup prt) {
list.add(childEntries.get(i)); parent = (PwGroupV4) prt;
} }
for ( int i = 0; i < numbersOfChildGroups(); i++ ) { public UUID getUUID() {
PwGroupV4 child = (PwGroupV4) childGroups.get(i); return uuid;
child.buildChildEntriesRecursive(list); }
}
public void setUUID(UUID uuid) {
} this.uuid = uuid;
}
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
@Override @Override
public PwGroupId getId() { public PwGroupId getId() {
@@ -138,78 +110,35 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
} }
@Override @Override
public String getName() { public PwDate getLocationChanged() {
return name;
}
@Override
public Date getLastMod() {
return parentGroupLastMod; 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 @Override
public void setLastAccessTime(Date date) { public void setLocationChanged(PwDate date) {
lastAccess = date;
}
@Override
public void setLastModificationTime(Date date) {
lastMod = date;
}
public void setLocationChanged(Date date) {
parentGroupLastMod = date; parentGroupLastMod = date;
} }
@Override
public long getUsageCount() {
return usageCount;
}
@Override
public void setUsageCount(long count) { public void setUsageCount(long count) {
usageCount = count; usageCount = count;
} }
@Override
public boolean expires() { public boolean expires() {
return expires; return expires;
} }
@Override
public void setExpires(boolean exp) { public void setExpires(boolean exp) {
expires = exp; expires = exp;
} }
@Override
public void setParent(PwGroup prt) {
parent = (PwGroupV4) prt;
}
@Override @Override
public boolean allowAddEntryIfIsRoot() { public boolean allowAddEntryIfIsRoot() {
return true; return true;
@@ -217,14 +146,70 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) { if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; 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; PwGroupV4 group = this;
while (group != null) { while (group != null) {
Boolean search = group.enableSearching; Boolean search = group.enableSearching;

View File

@@ -3,12 +3,8 @@ package com.keepassdroid.database;
import java.io.Serializable; import java.io.Serializable;
public abstract class PwIcon implements Serializable { public abstract class PwIcon implements Serializable {
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return false; return false;
} }
public void writeBytes() {
}
} }

View File

@@ -22,7 +22,7 @@ package com.keepassdroid.database;
import java.util.UUID; import java.util.UUID;
public class PwIconCustom extends PwIcon { public class PwIconCustom extends PwIcon {
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabaseV4.UUID_ZERO, new byte[0]); public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
public final UUID uuid; public final UUID uuid;
public byte[] imageData; public byte[] imageData;
@@ -32,6 +32,11 @@ public class PwIconCustom extends PwIcon {
imageData = data; imageData = data;
} }
public PwIconCustom(PwIconCustom icon) {
uuid = icon.uuid;
imageData = icon.imageData;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@@ -32,6 +32,10 @@ public class PwIconStandard extends PwIcon {
this.iconId = iconId; this.iconId = iconId;
} }
public PwIconStandard(PwIconStandard icon) {
this.iconId = icon.iconId;
}
@Override @Override
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return iconId == 0; return iconId == 0;

View File

@@ -21,12 +21,42 @@
package com.keepassdroid.database; package com.keepassdroid.database;
import java.io.Serializable; 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 * 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 * Type of available Nodes
@@ -48,12 +78,17 @@ public abstract class PwNode implements Serializable {
/** /**
* @return Visual icon * @return Visual icon
*/ */
public abstract PwIcon getIcon(); public PwIcon getIcon() {
return icon;
}
/** public PwIconStandard getIconStandard() {
* @return Creation date and time of the node return icon;
*/ }
public abstract Date getCreationTime();
public void setIcon(PwIconStandard icon) {
this.icon = icon;
}
/** /**
* Retrieve the parent node * Retrieve the parent node
@@ -66,6 +101,48 @@ public abstract class PwNode implements Serializable {
*/ */
public abstract void setParent(PwGroup parent); 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 * If the content (type, title, icon) is visually the same
* @param o Node to compare * @param o Node to compare

View File

@@ -47,14 +47,6 @@ public enum SortNodeEnum {
boolean ascending; boolean ascending;
boolean groupsBefore; boolean groupsBefore;
NodeComparator() {
this(true, true);
}
NodeComparator(boolean groupsBefore) {
this(true, groupsBefore);
}
NodeComparator(boolean ascending, boolean groupsBefore) { NodeComparator(boolean ascending, boolean groupsBefore) {
this.ascending = ascending; this.ascending = ascending;
this.groupsBefore = groupsBefore; this.groupsBefore = groupsBefore;
@@ -66,14 +58,6 @@ public enum SortNodeEnum {
*/ */
public static class NodeTitleComparator extends NodeComparator { public static class NodeTitleComparator extends NodeComparator {
public NodeTitleComparator() {
super();
}
public NodeTitleComparator(boolean groupsBefore) {
super(groupsBefore);
}
public NodeTitleComparator(boolean ascending, boolean groupsBefore) { public NodeTitleComparator(boolean ascending, boolean groupsBefore) {
super(ascending, groupsBefore); super(ascending, groupsBefore);
} }
@@ -84,7 +68,7 @@ public enum SortNodeEnum {
if (object1 instanceof PwGroup) { if (object1 instanceof PwGroup) {
if (object2 instanceof PwGroup) { if (object2 instanceof PwGroup) {
return new PwGroup.GroupNameComparator(ascending) return new GroupNameComparator(ascending)
.compare((PwGroup) object1, (PwGroup) object2); .compare((PwGroup) object1, (PwGroup) object2);
} else if (object2 instanceof PwEntry) { } else if (object2 instanceof PwEntry) {
if(groupsBefore) if(groupsBefore)
@@ -96,7 +80,7 @@ public enum SortNodeEnum {
} }
} else if (object1 instanceof PwEntry) { } else if (object1 instanceof PwEntry) {
if(object2 instanceof PwEntry) { if(object2 instanceof PwEntry) {
return new PwEntry.EntryNameComparator(ascending) return new EntryNameComparator(ascending)
.compare((PwEntry) object1, (PwEntry) object2); .compare((PwEntry) object1, (PwEntry) object2);
} else if (object2 instanceof PwGroup) { } else if (object2 instanceof PwGroup) {
if(groupsBefore) if(groupsBefore)
@@ -121,14 +105,6 @@ public enum SortNodeEnum {
*/ */
public static class NodeCreationComparator extends NodeComparator { public static class NodeCreationComparator extends NodeComparator {
public NodeCreationComparator() {
super();
}
public NodeCreationComparator(boolean groupsBefore) {
super(groupsBefore);
}
public NodeCreationComparator(boolean ascending, boolean groupsBefore) { public NodeCreationComparator(boolean ascending, boolean groupsBefore) {
super(ascending, groupsBefore); super(ascending, groupsBefore);
@@ -141,7 +117,7 @@ public enum SortNodeEnum {
if (object1 instanceof PwGroup) { if (object1 instanceof PwGroup) {
if (object2 instanceof PwGroup) { if (object2 instanceof PwGroup) {
return new PwGroup.GroupCreationComparator(ascending) return new GroupCreationComparator(ascending)
.compare((PwGroup) object1, (PwGroup) object2); .compare((PwGroup) object1, (PwGroup) object2);
} else if (object2 instanceof PwEntry) { } else if (object2 instanceof PwEntry) {
if(groupsBefore) if(groupsBefore)
@@ -153,7 +129,7 @@ public enum SortNodeEnum {
} }
} else if (object1 instanceof PwEntry) { } else if (object1 instanceof PwEntry) {
if(object2 instanceof PwEntry) { if(object2 instanceof PwEntry) {
return new PwEntry.EntryCreationComparator(ascending) return new EntryCreationComparator(ascending)
.compare((PwEntry) object1, (PwEntry) object2); .compare((PwEntry) object1, (PwEntry) object2);
} else if (object2 instanceof PwGroup) { } else if (object2 instanceof PwGroup) {
if(groupsBefore) if(groupsBefore)
@@ -164,8 +140,8 @@ public enum SortNodeEnum {
return -1; return -1;
} }
} }
int nodeCreationComp = object1.getCreationTime() int nodeCreationComp = object1.getCreationTime().getDate()
.compareTo(object2.getCreationTime()); .compareTo(object2.getCreationTime().getDate());
// If same creation, can be different // If same creation, can be different
if (nodeCreationComp == 0) { if (nodeCreationComp == 0) {
return object1.hashCode() - object2.hashCode(); return object1.hashCode() - object2.hashCode();
@@ -173,4 +149,118 @@ public enum SortNodeEnum {
return nodeCreationComp; 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;
}
}
} }

View File

@@ -56,7 +56,7 @@ public class AddGroup extends RunnableOnFinish {
// Generate new group // Generate new group
mGroup = pm.createGroup(); mGroup = pm.createGroup();
mGroup.initNewGroup(mName, pm.newGroupId()); mGroup.initNewGroup(mName, pm.newGroupId());
mGroup.icon = mDb.pm.iconFactory.getIcon(mIconID); mGroup.setIcon(mDb.pm.iconFactory.getIcon(mIconID));
pm.addGroupTo(mGroup, mParent); pm.addGroupTo(mGroup, mParent);
// Commit to disk // Commit to disk

View File

@@ -72,14 +72,14 @@ public class DeleteGroup extends RunnableOnFinish {
else { else {
// TODO tests // TODO tests
// Remove child entries // 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++ ) { for ( int i = 0; i < childEnt.size(); i++ ) {
DeleteEntry task = new DeleteEntry(mContext, mDb, childEnt.get(i), null, true); DeleteEntry task = new DeleteEntry(mContext, mDb, childEnt.get(i), null, true);
task.run(); task.run();
} }
// Remove child groups // 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++ ) { for ( int i = 0; i < childGrp.size(); i++ ) {
DeleteGroup task = new DeleteGroup(mContext, mDb, childGrp.get(i), null, true); DeleteGroup task = new DeleteGroup(mContext, mDb, childGrp.get(i), null, true);
task.run(); task.run();

View File

@@ -26,7 +26,7 @@ import android.net.Uri;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.exception.InvalidKeyFileException; import com.keepassdroid.database.exception.InvalidKeyFileException;
import com.keepassdroid.dialog.PasswordEncodingDialogHelper; import com.keepassdroid.dialogs.PasswordEncodingDialogHelper;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
import java.io.IOException; import java.io.IOException;

View File

@@ -40,7 +40,7 @@ public class UpdateEntry extends RunnableOnFinish {
// Keep backup of original values in case save fails // Keep backup of original values in case save fails
PwEntry backup; PwEntry backup;
backup = (PwEntry) mOldE.clone(); backup = mOldE.clone();
mFinish = new AfterUpdate(backup, finish); mFinish = new AfterUpdate(backup, finish);
} }

View File

@@ -340,31 +340,31 @@ public class ImporterV3 extends Importer {
// Ignore field // Ignore field
break; break;
case 0x0001 : case 0x0001 :
grp.groupId = LEDataInputStream.readInt(buf, offset); grp.setGroupId(LEDataInputStream.readInt(buf, offset));
break; break;
case 0x0002 : case 0x0002 :
grp.name = Types.readCString(buf, offset); grp.setName(Types.readCString(buf, offset));
break; break;
case 0x0003 : case 0x0003 :
grp.tCreation = new PwDate(buf, offset); grp.setCreationTime(new PwDate(buf, offset));
break; break;
case 0x0004 : case 0x0004 :
grp.tLastMod = new PwDate(buf, offset); grp.setLastModificationTime(new PwDate(buf, offset));
break; break;
case 0x0005 : case 0x0005 :
grp.tLastAccess = new PwDate(buf, offset); grp.setLastAccessTime(new PwDate(buf, offset));
break; break;
case 0x0006 : case 0x0006 :
grp.tExpire = new PwDate(buf, offset); grp.setExpiryTime(new PwDate(buf, offset));
break; break;
case 0x0007 : case 0x0007 :
grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset)); grp.setIcon(db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset)));
break; break;
case 0x0008 : case 0x0008 :
grp.level = LEDataInputStream.readUShort(buf, offset); grp.setLevel(LEDataInputStream.readUShort(buf, offset));
break; break;
case 0x0009 : case 0x0009 :
grp.flags = LEDataInputStream.readInt(buf, offset); grp.setFlags(LEDataInputStream.readInt(buf, offset));
break; break;
} }
} }
@@ -387,7 +387,7 @@ public class ImporterV3 extends Importer {
ent.setUUID(Types.bytestoUUID(buf, offset)); ent.setUUID(Types.bytestoUUID(buf, offset));
break; break;
case 0x0002 : case 0x0002 :
ent.groupId = LEDataInputStream.readInt(buf, offset); ent.setGroupId(LEDataInputStream.readInt(buf, offset));
break; break;
case 0x0003 : case 0x0003 :
int iconId = LEDataInputStream.readInt(buf, offset); int iconId = LEDataInputStream.readInt(buf, offset);
@@ -397,37 +397,37 @@ public class ImporterV3 extends Importer {
iconId = 0; iconId = 0;
} }
ent.icon = db.iconFactory.getIcon(iconId); ent.setIcon(db.iconFactory.getIcon(iconId));
break; break;
case 0x0004 : case 0x0004 :
ent.title = Types.readCString(buf, offset); ent.setTitle(Types.readCString(buf, offset));
break; break;
case 0x0005 : case 0x0005 :
ent.url = Types.readCString(buf, offset); ent.setUrl(Types.readCString(buf, offset));
break; break;
case 0x0006 : case 0x0006 :
ent.username = Types.readCString(buf, offset); ent.setUsername(Types.readCString(buf, offset));
break; break;
case 0x0007 : case 0x0007 :
ent.setPassword(buf, offset, Types.strlen(buf, offset)); ent.setPassword(buf, offset, Types.strlen(buf, offset));
break; break;
case 0x0008 : case 0x0008 :
ent.additional = Types.readCString(buf, offset); ent.setNotes(Types.readCString(buf, offset));
break; break;
case 0x0009 : case 0x0009 :
ent.tCreation = new PwDate(buf, offset); ent.setCreationTime(new PwDate(buf, offset));
break; break;
case 0x000A : case 0x000A :
ent.tLastMod = new PwDate(buf, offset); ent.setLastModificationTime(new PwDate(buf, offset));
break; break;
case 0x000B : case 0x000B :
ent.tLastAccess = new PwDate(buf, offset); ent.setLastAccessTime(new PwDate(buf, offset));
break; break;
case 0x000C : case 0x000C :
ent.tExpire = new PwDate(buf, offset); ent.setExpiryTime(new PwDate(buf, offset));
break; break;
case 0x000D : case 0x000D :
ent.binaryDesc = Types.readCString(buf, offset); ent.setBinaryDesc(Types.readCString(buf, offset));
break; break;
case 0x000E : case 0x000E :
ent.setBinaryData(buf, offset, fieldSize); ent.setBinaryData(buf, offset, fieldSize);

View File

@@ -46,6 +46,8 @@ import org.xmlpull.v1.XmlPullParserFactory;
import biz.source_code.base64Coder.Base64Coder; import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDate;
import com.keepassdroid.tasks.UpdateStatus; import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory; import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.PwStreamCipherFactory; import com.keepassdroid.crypto.PwStreamCipherFactory;
@@ -308,7 +310,7 @@ public class ImporterV4 extends Importer {
private boolean entryInHistory = false; private boolean entryInHistory = false;
private PwEntryV4 ctxHistoryBase = null; private PwEntryV4 ctxHistoryBase = null;
private PwDeletedObject ctxDeletedObject = null; private PwDeletedObject ctxDeletedObject = null;
private UUID customIconID = PwDatabaseV4.UUID_ZERO; private UUID customIconID = PwDatabase.UUID_ZERO;
private byte[] customIconData; private byte[] customIconData;
private String customDataKey = null; private String customDataKey = null;
private String customDataValue = null; private String customDataValue = null;
@@ -557,38 +559,38 @@ public class ImporterV4 extends Importer {
case Group: case Group:
if ( name.equalsIgnoreCase(ElemUuid) ) { if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxGroup.uuid = ReadUuid(xpp); ctxGroup.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemName) ) { } else if ( name.equalsIgnoreCase(ElemName) ) {
ctxGroup.name = ReadString(xpp); ctxGroup.setName(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemNotes) ) { } else if ( name.equalsIgnoreCase(ElemNotes) ) {
ctxGroup.notes = ReadString(xpp); ctxGroup.setNotes(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemIcon) ) { } 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) ) { } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxGroup.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); ctxGroup.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(ElemTimes) ) { } else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.GroupTimes, xpp); return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemIsExpanded) ) { } else if ( name.equalsIgnoreCase(ElemIsExpanded) ) {
ctxGroup.isExpanded = ReadBool(xpp, true); ctxGroup.setExpanded(ReadBool(xpp, true));
} else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) { } else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) {
ctxGroup.defaultAutoTypeSequence = ReadString(xpp); ctxGroup.setDefaultAutoTypeSequence(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) { } else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) {
ctxGroup.enableAutoType = StringToBoolean(ReadString(xpp)); ctxGroup.setEnableAutoType(StringToBoolean(ReadString(xpp)));
} else if ( name.equalsIgnoreCase(ElemEnableSearching) ) { } else if ( name.equalsIgnoreCase(ElemEnableSearching) ) {
ctxGroup.enableSearching = StringToBoolean(ReadString(xpp)); ctxGroup.setEnableSearching(StringToBoolean(ReadString(xpp)));
} else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) { } else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) {
ctxGroup.lastTopVisibleEntry = ReadUuid(xpp); ctxGroup.setLastTopVisibleEntry(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemCustomData) ) { } else if ( name.equalsIgnoreCase(ElemCustomData) ) {
return SwitchContext(ctx, KdbContext.GroupCustomData, xpp); return SwitchContext(ctx, KdbContext.GroupCustomData, xpp);
} else if ( name.equalsIgnoreCase(ElemGroup) ) { } else if ( name.equalsIgnoreCase(ElemGroup) ) {
ctxGroup = new PwGroupV4(); ctxGroup = new PwGroupV4();
ctxGroups.peek().AddGroup(ctxGroup, true); ctxGroups.peek().AddGroup(ctxGroup);
ctxGroups.push(ctxGroup); ctxGroups.push(ctxGroup);
return SwitchContext(ctx, KdbContext.Group, xpp); return SwitchContext(ctx, KdbContext.Group, xpp);
} else if ( name.equalsIgnoreCase(ElemEntry) ) { } else if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4(); ctxEntry = new PwEntryV4();
ctxGroup.AddEntry(ctxEntry, true); ctxGroup.AddEntry(ctxEntry);
entryInHistory = false; entryInHistory = false;
return SwitchContext(ctx, KdbContext.Entry, xpp); return SwitchContext(ctx, KdbContext.Entry, xpp);
@@ -618,17 +620,17 @@ public class ImporterV4 extends Importer {
if ( name.equalsIgnoreCase(ElemUuid) ) { if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxEntry.setUUID(ReadUuid(xpp)); ctxEntry.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemIcon) ) { } 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) ) { } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxEntry.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); ctxEntry.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(ElemFgColor) ) { } else if ( name.equalsIgnoreCase(ElemFgColor) ) {
ctxEntry.foregroundColor = ReadString(xpp); ctxEntry.setForegroundColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemBgColor) ) { } else if ( name.equalsIgnoreCase(ElemBgColor) ) {
ctxEntry.backgroupColor = ReadString(xpp); ctxEntry.setBackgroupColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) { } else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) {
ctxEntry.overrideURL = ReadString(xpp); ctxEntry.setOverrideURL(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemTags) ) { } else if ( name.equalsIgnoreCase(ElemTags) ) {
ctxEntry.tags = ReadString(xpp); ctxEntry.setTags(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemTimes) ) { } else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.EntryTimes, xpp); return SwitchContext(ctx, KdbContext.EntryTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemString) ) { } else if ( name.equalsIgnoreCase(ElemString) ) {
@@ -679,19 +681,19 @@ public class ImporterV4 extends Importer {
} }
if ( name.equalsIgnoreCase(ElemLastModTime) ) { if ( name.equalsIgnoreCase(ElemLastModTime) ) {
tl.setLastModificationTime(ReadTime(xpp)); tl.setLastModificationTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemCreationTime) ) { } else if ( name.equalsIgnoreCase(ElemCreationTime) ) {
tl.setCreationTime(ReadTime(xpp)); tl.setCreationTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) { } else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) {
tl.setLastAccessTime(ReadTime(xpp)); tl.setLastAccessTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpiryTime) ) { } else if ( name.equalsIgnoreCase(ElemExpiryTime) ) {
tl.setExpiryTime(ReadTime(xpp)); tl.setExpiryTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpires) ) { } else if ( name.equalsIgnoreCase(ElemExpires) ) {
tl.setExpires(ReadBool(xpp, false)); tl.setExpires(ReadBool(xpp, false));
} else if ( name.equalsIgnoreCase(ElemUsageCount) ) { } else if ( name.equalsIgnoreCase(ElemUsageCount) ) {
tl.setUsageCount(ReadULong(xpp, 0)); tl.setUsageCount(ReadULong(xpp, 0));
} else if ( name.equalsIgnoreCase(ElemLocationChanged) ) { } else if ( name.equalsIgnoreCase(ElemLocationChanged) ) {
tl.setLocationChanged(ReadTime(xpp)); tl.setLocationChanged(ReadPwTime(xpp));
} else { } else {
ReadUnknown(xpp); ReadUnknown(xpp);
} }
@@ -717,11 +719,11 @@ public class ImporterV4 extends Importer {
case EntryAutoType: case EntryAutoType:
if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) { if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) {
ctxEntry.autoType.enabled = ReadBool(xpp, true); ctxEntry.getAutoType().enabled = ReadBool(xpp, true);
} else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) {
ctxEntry.autoType.obfuscationOptions = ReadUInt(xpp, 0); ctxEntry.getAutoType().obfuscationOptions = ReadUInt(xpp, 0);
} else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) {
ctxEntry.autoType.defaultSequence = ReadString(xpp); ctxEntry.getAutoType().defaultSequence = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) {
return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp); return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp);
} else { } else {
@@ -742,7 +744,7 @@ public class ImporterV4 extends Importer {
case EntryHistory: case EntryHistory:
if ( name.equalsIgnoreCase(ElemEntry) ) { if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4(); ctxEntry = new PwEntryV4();
ctxHistoryBase.history.add(ctxEntry); ctxHistoryBase.addToHistory(ctxEntry);
entryInHistory = true; entryInHistory = true;
return SwitchContext(ctx, KdbContext.Entry, xpp); return SwitchContext(ctx, KdbContext.Entry, xpp);
@@ -795,13 +797,13 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) { } else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) {
return KdbContext.Meta; return KdbContext.Meta;
} else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) { } 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); PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
db.customIcons.add(icon); db.customIcons.add(icon);
db.iconFactory.put(icon); db.iconFactory.put(icon);
} else assert(false); } else assert(false);
customIconID = PwDatabaseV4.UUID_ZERO; customIconID = PwDatabase.UUID_ZERO;
customIconData = null; customIconData = null;
return KdbContext.CustomIcons; return KdbContext.CustomIcons;
@@ -819,8 +821,8 @@ public class ImporterV4 extends Importer {
return KdbContext.CustomData; return KdbContext.CustomData;
} else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) { } else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) {
if ( ctxGroup.uuid == null || ctxGroup.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { if ( ctxGroup.getUUID() == null || ctxGroup.getUUID().equals(PwDatabase.UUID_ZERO) ) {
ctxGroup.uuid = UUID.randomUUID(); ctxGroup.setUUID(UUID.randomUUID());
} }
ctxGroups.pop(); ctxGroups.pop();
@@ -838,7 +840,7 @@ public class ImporterV4 extends Importer {
return KdbContext.Group; return KdbContext.Group;
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { } else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
if (groupCustomDataKey != null && groupCustomDataValue != null) { if (groupCustomDataKey != null && groupCustomDataValue != null) {
ctxGroup.customData.put(groupCustomDataKey, groupCustomDataKey); ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
} else { } else {
assert(false); assert(false);
} }
@@ -849,8 +851,8 @@ public class ImporterV4 extends Importer {
return KdbContext.GroupCustomData; return KdbContext.GroupCustomData;
} else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) { } else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) {
if ( ctxEntry.uuid == null || ctxEntry.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { if ( ctxEntry.getUUID() == null || ctxEntry.getUUID().equals(PwDatabase.UUID_ZERO) ) {
ctxEntry.uuid = UUID.randomUUID(); ctxEntry.setUUID(UUID.randomUUID());
} }
if ( entryInHistory ) { if ( entryInHistory ) {
@@ -868,7 +870,7 @@ public class ImporterV4 extends Importer {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) { } else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) {
ctxEntry.binaries.put(ctxBinaryName, ctxBinaryValue); ctxEntry.putProtectedBinary(ctxBinaryName, ctxBinaryValue);
ctxBinaryName = null; ctxBinaryName = null;
ctxBinaryValue = null; ctxBinaryValue = null;
@@ -876,7 +878,7 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) { } else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) { } else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) {
ctxEntry.autoType.put(ctxATName, ctxATSeq); ctxEntry.getAutoType().put(ctxATName, ctxATSeq);
ctxATName = null; ctxATName = null;
ctxATSeq = null; ctxATSeq = null;
@@ -885,7 +887,7 @@ public class ImporterV4 extends Importer {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { } else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
if (entryCustomDataKey != null && entryCustomDataValue != null) { if (entryCustomDataKey != null && entryCustomDataValue != null) {
ctxEntry.customData.put(entryCustomDataKey, entryCustomDataValue); ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
} else { } else {
assert(false); assert(false);
} }
@@ -912,6 +914,10 @@ public class ImporterV4 extends Importer {
throw new RuntimeException("Invalid end element: Context " + contextName + "End element: " + name); 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 { private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
String sDate = ReadString(xpp); String sDate = ReadString(xpp);
@@ -980,7 +986,7 @@ public class ImporterV4 extends Importer {
String encoded = ReadString(xpp); String encoded = ReadString(xpp);
if (encoded == null || encoded.length() == 0 ) { 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 // TODO: Switch to framework Base64 once API level 8 is the minimum

View File

@@ -261,7 +261,7 @@ public class PwDbV3Output extends PwDbOutput {
// Recurse over children // Recurse over children
for ( int i = 0; i < group.numbersOfChildGroups(); i++ ) { for ( int i = 0; i < group.numbersOfChildGroups(); i++ ) {
sortGroup((PwGroupV3) group.childGroups.get(i), groupList); sortGroup((PwGroupV3) group.getChildGroupAt(i), groupList);
} }
} }

View File

@@ -40,7 +40,7 @@ import com.keepassdroid.database.PwDefsV4;
import com.keepassdroid.database.PwDeletedObject; import com.keepassdroid.database.PwDeletedObject;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4; 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.PwGroup;
import com.keepassdroid.database.PwGroupV4; import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.PwIconCustom; import com.keepassdroid.database.PwIconCustom;
@@ -245,7 +245,7 @@ public class PwDbV4Output extends PwDbOutput {
while(true) { while(true) {
try { try {
if (group.parent == groupStack.peek()) { if (group.getParent() == groupStack.peek()) {
groupStack.push(group); groupStack.push(group);
startGroup(group); startGroup(group);
break; break;
@@ -434,21 +434,21 @@ public class PwDbV4Output extends PwDbOutput {
private void startGroup(PwGroupV4 group) throws IllegalArgumentException, IllegalStateException, IOException { private void startGroup(PwGroupV4 group) throws IllegalArgumentException, IllegalStateException, IOException {
xml.startTag(null, ElemGroup); xml.startTag(null, ElemGroup);
writeObject(ElemUuid, group.uuid); writeObject(ElemUuid, group.getUUID());
writeObject(ElemName, group.name); writeObject(ElemName, group.getName());
writeObject(ElemNotes, group.notes); writeObject(ElemNotes, group.getNotes());
writeObject(ElemIcon, group.icon.iconId); writeObject(ElemIcon, group.getIconStandard().iconId);
if (!group.customIcon.equals(PwIconCustom.ZERO)) { if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(ElemCustomIconID, group.customIcon.uuid); writeObject(ElemCustomIconID, group.getCustomIcon().uuid);
} }
writeList(ElemTimes, group); writeList(ElemTimes, group);
writeObject(ElemIsExpanded, group.isExpanded); writeObject(ElemIsExpanded, group.isExpanded());
writeObject(ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence); writeObject(ElemGroupDefaultAutoTypeSeq, group.getDefaultAutoTypeSequence());
writeObject(ElemEnableAutoType, group.enableAutoType); writeObject(ElemEnableAutoType, group.getEnableAutoType());
writeObject(ElemEnableSearching, group.enableSearching); writeObject(ElemEnableSearching, group.getEnableSearching());
writeObject(ElemLastTopVisibleEntry, group.lastTopVisibleEntry); writeObject(ElemLastTopVisibleEntry, group.getLastTopVisibleEntry());
} }
@@ -461,28 +461,28 @@ public class PwDbV4Output extends PwDbOutput {
xml.startTag(null, ElemEntry); xml.startTag(null, ElemEntry);
writeObject(ElemUuid, entry.uuid); writeObject(ElemUuid, entry.getUUID());
writeObject(ElemIcon, entry.icon.iconId); writeObject(ElemIcon, entry.getIconStandard().iconId);
if (!entry.customIcon.equals(PwIconCustom.ZERO)) { if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(ElemCustomIconID, entry.customIcon.uuid); writeObject(ElemCustomIconID, entry.getCustomIcon().uuid);
} }
writeObject(ElemFgColor, entry.foregroundColor); writeObject(ElemFgColor, entry.getForegroundColor());
writeObject(ElemBgColor, entry.backgroupColor); writeObject(ElemBgColor, entry.getBackgroupColor());
writeObject(ElemOverrideUrl, entry.overrideURL); writeObject(ElemOverrideUrl, entry.getOverrideURL());
writeObject(ElemTags, entry.tags); writeObject(ElemTags, entry.getTags());
writeList(ElemTimes, entry); writeList(ElemTimes, entry);
writeList(entry.getFields(), true); writeList(entry.getFields(), true);
writeList(entry.binaries); writeList(entry.getBinaries());
writeList(ElemAutoType, entry.autoType); writeList(ElemAutoType, entry.getAutoType());
if (!isHistory) { if (!isHistory) {
writeList(ElemHistory, entry.history, true); writeList(ElemHistory, entry.getHistory(), true);
} else { } else {
assert(entry.history.size() == 0); assert(entry.sizeOfHistory() == 0);
} }
xml.endTag(null, ElemEntry); xml.endTag(null, ElemEntry);
@@ -755,13 +755,13 @@ public class PwDbV4Output extends PwDbOutput {
xml.startTag(null, name); xml.startTag(null, name);
writeObject(ElemLastModTime, it.getLastModificationTime()); writeObject(ElemLastModTime, it.getLastModificationTime().getDate());
writeObject(ElemCreationTime, it.getCreationTime()); writeObject(ElemCreationTime, it.getCreationTime().getDate());
writeObject(ElemLastAccessTime, it.getLastAccessTime()); writeObject(ElemLastAccessTime, it.getLastAccessTime().getDate());
writeObject(ElemExpiryTime, it.getExpiryTime()); writeObject(ElemExpiryTime, it.getExpiryTime().getDate());
writeObject(ElemExpires, it.expires()); writeObject(ElemExpires, it.expires());
writeObject(ElemUsageCount, it.getUsageCount()); writeObject(ElemUsageCount, it.getUsageCount());
writeObject(ElemLocationChanged, it.getLocationChanged()); writeObject(ElemLocationChanged, it.getLocationChanged().getDate());
xml.endTag(null, name); xml.endTag(null, name);
} }

View File

@@ -79,27 +79,27 @@ public class PwEntryOutputV3 {
// Group ID // Group ID
mOS.write(GROUPID_FIELD_TYPE); mOS.write(GROUPID_FIELD_TYPE);
mOS.write(LONG_FOUR); mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.groupId)); mOS.write(LEDataOutputStream.writeIntBuf(mPE.getGroupId()));
// Image ID // Image ID
mOS.write(IMAGEID_FIELD_TYPE); mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(LONG_FOUR); mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.icon.iconId)); mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
// Title // Title
//byte[] title = mPE.title.getBytes("UTF-8"); //byte[] title = mPE.title.getBytes("UTF-8");
mOS.write(TITLE_FIELD_TYPE); mOS.write(TITLE_FIELD_TYPE);
int titleLen = Types.writeCString(mPE.title, mOS); int titleLen = Types.writeCString(mPE.getTitle(), mOS);
outputBytes += titleLen; outputBytes += titleLen;
// URL // URL
mOS.write(URL_FIELD_TYPE); mOS.write(URL_FIELD_TYPE);
int urlLen = Types.writeCString(mPE.url, mOS); int urlLen = Types.writeCString(mPE.getUrl(), mOS);
outputBytes += urlLen; outputBytes += urlLen;
// Username // Username
mOS.write(USERNAME_FIELD_TYPE); mOS.write(USERNAME_FIELD_TYPE);
int userLen = Types.writeCString(mPE.username, mOS); int userLen = Types.writeCString(mPE.getUsername(), mOS);
outputBytes += userLen; outputBytes += userLen;
// Password // Password
@@ -112,24 +112,24 @@ public class PwEntryOutputV3 {
// Additional // Additional
mOS.write(ADDITIONAL_FIELD_TYPE); mOS.write(ADDITIONAL_FIELD_TYPE);
int addlLen = Types.writeCString(mPE.additional, mOS); int addlLen = Types.writeCString(mPE.getNotes(), mOS);
outputBytes += addlLen; outputBytes += addlLen;
// Create date // Create date
writeDate(CREATE_FIELD_TYPE, mPE.tCreation.getCDate()); writeDate(CREATE_FIELD_TYPE, mPE.getCreationTime().getCDate());
// Modification date // Modification date
writeDate(MOD_FIELD_TYPE, mPE.tLastMod.getCDate()); writeDate(MOD_FIELD_TYPE, mPE.getLastModificationTime().getCDate());
// Access date // Access date
writeDate(ACCESS_FIELD_TYPE, mPE.tLastAccess.getCDate()); writeDate(ACCESS_FIELD_TYPE, mPE.getLastAccessTime().getCDate());
// Expiration date // Expiration date
writeDate(EXPIRE_FIELD_TYPE, mPE.tExpire.getCDate()); writeDate(EXPIRE_FIELD_TYPE, mPE.getExpiryTime().getCDate());
// Binary desc // Binary desc
mOS.write(BINARY_DESC_FIELD_TYPE); mOS.write(BINARY_DESC_FIELD_TYPE);
int descLen = Types.writeCString(mPE.binaryDesc, mOS); int descLen = Types.writeCString(mPE.getBinaryDesc(), mOS);
outputBytes += descLen; outputBytes += descLen;
// Binary data // Binary data

View File

@@ -65,46 +65,46 @@ public class PwGroupOutputV3 {
// Group ID // Group ID
mOS.write(GROUPID_FIELD_TYPE); mOS.write(GROUPID_FIELD_TYPE);
mOS.write(GROUPID_FIELD_SIZE); mOS.write(GROUPID_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.groupId)); mOS.write(LEDataOutputStream.writeIntBuf(mPG.getGroupId()));
// Name // Name
mOS.write(NAME_FIELD_TYPE); mOS.write(NAME_FIELD_TYPE);
Types.writeCString(mPG.name, mOS); Types.writeCString(mPG.getName(), mOS);
// Create date // Create date
mOS.write(CREATE_FIELD_TYPE); mOS.write(CREATE_FIELD_TYPE);
mOS.write(DATE_FIELD_SIZE); mOS.write(DATE_FIELD_SIZE);
mOS.write(mPG.tCreation.getCDate()); mOS.write(mPG.getCreationTime().getCDate());
// Modification date // Modification date
mOS.write(MOD_FIELD_TYPE); mOS.write(MOD_FIELD_TYPE);
mOS.write(DATE_FIELD_SIZE); mOS.write(DATE_FIELD_SIZE);
mOS.write(mPG.tLastMod.getCDate()); mOS.write(mPG.getLastModificationTime().getCDate());
// Access date // Access date
mOS.write(ACCESS_FIELD_TYPE); mOS.write(ACCESS_FIELD_TYPE);
mOS.write(DATE_FIELD_SIZE); mOS.write(DATE_FIELD_SIZE);
mOS.write(mPG.tLastAccess.getCDate()); mOS.write(mPG.getLastAccessTime().getCDate());
// Expiration date // Expiration date
mOS.write(EXPIRE_FIELD_TYPE); mOS.write(EXPIRE_FIELD_TYPE);
mOS.write(DATE_FIELD_SIZE); mOS.write(DATE_FIELD_SIZE);
mOS.write(mPG.tExpire.getCDate()); mOS.write(mPG.getExpiryTime().getCDate());
// Image ID // Image ID
mOS.write(IMAGEID_FIELD_TYPE); mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(IMAGEID_FIELD_SIZE); mOS.write(IMAGEID_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.icon.iconId)); mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
// Level // Level
mOS.write(LEVEL_FIELD_TYPE); mOS.write(LEVEL_FIELD_TYPE);
mOS.write(LEVEL_FIELD_SIZE); mOS.write(LEVEL_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeUShortBuf(mPG.level)); mOS.write(LEDataOutputStream.writeUShortBuf(mPG.getLevel()));
// Flags // Flags
mOS.write(FLAGS_FIELD_TYPE); mOS.write(FLAGS_FIELD_TYPE);
mOS.write(FLAGS_FIELD_SIZE); mOS.write(FLAGS_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.flags)); mOS.write(LEDataOutputStream.writeIntBuf(mPG.getFlags()));
// End // End
mOS.write(END_FIELD_TYPE); mOS.write(END_FIELD_TYPE);

View File

@@ -19,9 +19,10 @@
*/ */
package com.keepassdroid.database.security; package com.keepassdroid.database.security;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
public class ProtectedBinary { public class ProtectedBinary implements Serializable {
public final static ProtectedBinary EMPTY = new ProtectedBinary(); public final static ProtectedBinary EMPTY = new ProtectedBinary();

View File

@@ -40,7 +40,6 @@ public class ProtectedString implements Serializable {
public ProtectedString() { public ProtectedString() {
this(false, ""); this(false, "");
} }
public ProtectedString(boolean enableProtection, String string) { public ProtectedString(boolean enableProtection, String string) {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
@@ -39,7 +39,7 @@ import android.widget.Toast;
import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.view.KeyFileHelper; import com.keepassdroid.fileselect.KeyFileHelper;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
public class AssignMasterKeyDialogFragment extends DialogFragment { public class AssignMasterKeyDialogFragment extends DialogFragment {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;

View File

@@ -17,18 +17,16 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
@@ -57,6 +55,7 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
private CompoundButton spaceBox; private CompoundButton spaceBox;
private CompoundButton specialsBox; private CompoundButton specialsBox;
private CompoundButton bracketsBox; private CompoundButton bracketsBox;
private CompoundButton extendedBox;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -76,20 +75,21 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
LayoutInflater inflater = getActivity().getLayoutInflater(); LayoutInflater inflater = getActivity().getLayoutInflater();
root = inflater.inflate(R.layout.generate_password, null); 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); uppercaseBox = root.findViewById(R.id.cb_uppercase);
lowercaseBox = (CompoundButton) root.findViewById(R.id.cb_lowercase); lowercaseBox = root.findViewById(R.id.cb_lowercase);
digitsBox = (CompoundButton) root.findViewById(R.id.cb_digits); digitsBox = root.findViewById(R.id.cb_digits);
minusBox = (CompoundButton) root.findViewById(R.id.cb_minus); minusBox = root.findViewById(R.id.cb_minus);
underlineBox = (CompoundButton) root.findViewById(R.id.cb_underline); underlineBox = root.findViewById(R.id.cb_underline);
spaceBox = (CompoundButton) root.findViewById(R.id.cb_space); spaceBox = root.findViewById(R.id.cb_space);
specialsBox = (CompoundButton) root.findViewById(R.id.cb_specials); specialsBox = root.findViewById(R.id.cb_specials);
bracketsBox = (CompoundButton) root.findViewById(R.id.cb_brackets); bracketsBox = root.findViewById(R.id.cb_brackets);
extendedBox = root.findViewById(R.id.cb_extended);
assignDefaultCharacters(); assignDefaultCharacters();
SeekBar seekBar = (SeekBar) root.findViewById(R.id.seekbar_length); SeekBar seekBar = root.findViewById(R.id.seekbar_length);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -102,34 +102,25 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) {} 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); Button genPassButton = root.findViewById(R.id.generate_password_button);
genPassButton.setOnClickListener(new OnClickListener() { genPassButton.setOnClickListener(v -> fillPassword());
public void onClick(View v) {
fillPassword();
}
});
builder.setView(root) builder.setView(root)
.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.accept, (dialog, id) -> {
@Override EditText password = root.findViewById(R.id.password);
public void onClick(DialogInterface dialog, int id) { Bundle bundle = new Bundle();
EditText password = (EditText) root.findViewById(R.id.password); bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
Bundle bundle = new Bundle(); mListener.acceptPassword(bundle);
bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
mListener.acceptPassword(bundle);
dismiss(); dismiss();
}
}) })
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel, (dialog, id) -> {
public void onClick(DialogInterface dialog, int id) { Bundle bundle = new Bundle();
Bundle bundle = new Bundle(); mListener.cancelPassword(bundle);
mListener.cancelPassword(bundle);
dismiss(); dismiss();
}
}); });
// Pre-populate a password to possibly save the user a few clicks // Pre-populate a password to possibly save the user a few clicks
@@ -147,9 +138,10 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
spaceBox.setChecked(false); spaceBox.setChecked(false);
specialsBox.setChecked(false); specialsBox.setChecked(false);
bracketsBox.setChecked(false); bracketsBox.setChecked(false);
extendedBox.setChecked(false);
Set<String> defaultPasswordChars = Set<String> defaultPasswordChars =
PreferencesUtil.getDefaultPasswordCharacters(getContext().getApplicationContext()); PreferencesUtil.getDefaultPasswordCharacters(getContext());
for(String passwordChar : defaultPasswordChars) { for(String passwordChar : defaultPasswordChars) {
if (passwordChar.equals(getString(R.string.value_password_uppercase))) { if (passwordChar.equals(getString(R.string.value_password_uppercase))) {
uppercaseBox.setChecked(true); uppercaseBox.setChecked(true);
@@ -175,11 +167,14 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
else if (passwordChar.equals(getString(R.string.value_password_brackets))) { else if (passwordChar.equals(getString(R.string.value_password_brackets))) {
bracketsBox.setChecked(true); bracketsBox.setChecked(true);
} }
else if (passwordChar.equals(getString(R.string.value_password_extended))) {
extendedBox.setChecked(true);
}
} }
} }
private void fillPassword() { private void fillPassword() {
EditText txtPassword = (EditText) root.findViewById(R.id.password); EditText txtPassword = root.findViewById(R.id.password);
txtPassword.setText(generatePassword()); txtPassword.setText(generatePassword());
} }
@@ -197,7 +192,8 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
underlineBox.isChecked(), underlineBox.isChecked(),
spaceBox.isChecked(), spaceBox.isChecked(),
specialsBox.isChecked(), specialsBox.isChecked(),
bracketsBox.isChecked()); bracketsBox.isChecked(),
extendedBox.isChecked());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Toast.makeText(getContext(), R.string.error_wrong_length, Toast.LENGTH_LONG).show(); Toast.makeText(getContext(), R.string.error_wrong_length, Toast.LENGTH_LONG).show();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.dialog; package com.keepassdroid.dialogs;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.dialog; package com.keepassdroid.dialogs;
import android.content.Context; import android.content.Context;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.fragments; package com.keepassdroid.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;

View File

@@ -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();
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.dialog; package com.keepassdroid.dialogs;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;

View File

@@ -20,69 +20,42 @@
package com.keepassdroid.fileselect; package com.keepassdroid.fileselect;
import android.app.Dialog; 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.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;
import android.widget.Button; import android.widget.Button;
import com.kunzisoft.keepass.R;
import com.keepassdroid.utils.Util; import com.keepassdroid.utils.Util;
import com.kunzisoft.keepass.R;
public class BrowserDialog extends Dialog { public class BrowserDialog extends DialogFragment {
public BrowserDialog(Context context) {
super(context);
}
@NonNull
@Override @Override
protected void onCreate(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
setContentView(R.layout.browser_install); // Get the layout inflater
setTitle(R.string.file_browser); LayoutInflater inflater = getActivity().getLayoutInflater();
View root = inflater.inflate(R.layout.browser_install, null);
Button cancel = (Button) findViewById(R.id.cancel); builder.setView(root)
cancel.setOnClickListener(new View.OnClickListener() { .setNegativeButton(R.string.cancel, (dialog, id) -> { });
public void onClick(View v) { Button market = root.findViewById(R.id.install_market);
BrowserDialog.this.cancel(); market.setOnClickListener((view) -> {
} Util.gotoUrl(getContext(), R.string.filemanager_play_store);
dismiss();
}); });
Button market = (Button) findViewById(R.id.install_market); Button web = root.findViewById(R.id.install_web);
market.setOnClickListener(new View.OnClickListener() { web.setOnClickListener(view -> {
Util.gotoUrl(getContext(), R.string.filemanager_f_droid);
public void onClick(View v) { dismiss();
Util.gotoUrl(getContext(), R.string.oi_filemanager_market); });
BrowserDialog.this.cancel();
} return builder.create();
});
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;
} }
} }

View File

@@ -22,7 +22,6 @@ package com.keepassdroid.fileselect;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.assist.AssistStructure; import android.app.assist.AssistStructure;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@@ -49,17 +48,17 @@ import com.keepassdroid.autofill.AutofillHelper;
import com.keepassdroid.database.edit.CreateDB; import com.keepassdroid.database.edit.CreateDB;
import com.keepassdroid.database.edit.FileOnFinish; import com.keepassdroid.database.edit.FileOnFinish;
import com.keepassdroid.database.exception.ContentFileNotFoundException; import com.keepassdroid.database.exception.ContentFileNotFoundException;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment; import com.keepassdroid.dialogs.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.CreateFileDialogFragment; import com.keepassdroid.dialogs.CreateFileDialogFragment;
import com.keepassdroid.password.AssignPasswordHelper;
import com.keepassdroid.password.PasswordActivity; import com.keepassdroid.password.PasswordActivity;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.stylish.StylishActivity; import com.keepassdroid.stylish.StylishActivity;
import com.keepassdroid.tasks.ProgressTask; import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.MenuUtil; import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.view.AssignPasswordHelper;
import com.keepassdroid.view.FileNameView; import com.keepassdroid.view.FileNameView;
import com.keepassdroid.view.KeyFileHelper;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import java.io.File; import java.io.File;
@@ -103,6 +102,8 @@ public class FileSelectActivity extends StylishActivity implements
private KeyFileHelper keyFileHelper; private KeyFileHelper keyFileHelper;
private String defaultPath;
public static void launch(Activity activity) { public static void launch(Activity activity) {
Intent intent = new Intent(activity, FileSelectActivity.class); Intent intent = new Intent(activity, FileSelectActivity.class);
// only to avoid visible flickering when redirecting // only to avoid visible flickering when redirecting
@@ -142,11 +143,11 @@ public class FileSelectActivity extends StylishActivity implements
fileNameView = (FileNameView) findViewById(R.id.file_select); fileNameView = (FileNameView) findViewById(R.id.file_select);
// Set the initial value of the filename // 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_path_default)
+ getString(R.string.database_file_name_default) + getString(R.string.database_file_name_default)
+ getString(R.string.database_file_extension_default); + getString(R.string.database_file_extension_default);
openFileNameView.setText(defaultPath); openFileNameView.setHint(defaultPath);
RecyclerView mListFiles = (RecyclerView) findViewById(R.id.file_list); RecyclerView mListFiles = (RecyclerView) findViewById(R.id.file_list);
mListFiles.setLayoutManager(new LinearLayoutManager(this)); mListFiles.setLayoutManager(new LinearLayoutManager(this));
@@ -159,53 +160,24 @@ public class FileSelectActivity extends StylishActivity implements
// Open button // Open button
View openButton = findViewById(R.id.open_database); View openButton = findViewById(R.id.open_database);
openButton.setOnClickListener(new View.OnClickListener() { openButton.setOnClickListener(v -> {
String fileName = openFileNameView.getText().toString();
public void onClick(View v) { if (fileName.isEmpty())
String fileName = openFileNameView.getText().toString(); fileName = defaultPath;
try { launchPasswordActivityWithPath(fileName);
AssistStructure assistStructure = null; });
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
if (assistStructure != null) {
PasswordActivity.launch(FileSelectActivity.this,
fileName,
assistStructure);
}
}
if (assistStructure == null) {
PasswordActivity.launch(FileSelectActivity.this, fileName);
}
}
catch (ContentFileNotFoundException e) {
Toast.makeText(FileSelectActivity.this,
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();
}
}
});
// Create button // Create button
View createButton = findViewById(R.id.create_database); View createButton = findViewById(R.id.create_database);
createButton.setOnClickListener(new View.OnClickListener() { createButton.setOnClickListener(v ->
public void onClick(View v) {
FileSelectActivityPermissionsDispatcher FileSelectActivityPermissionsDispatcher
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this); .openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this)
} );
});
keyFileHelper = new KeyFileHelper(this); keyFileHelper = new KeyFileHelper(this);
View browseButton = findViewById(R.id.browse_button); View browseButton = findViewById(R.id.browse_button);
browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener( browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
new KeyFileHelper.ClickDataUriCallback() { () -> Uri.parse("file://" + openFileNameView.getText().toString())));
@Override
public Uri onRequestIntentFilePicker() {
return Uri.parse("file://" + openFileNameView.getText().toString());
}
}));
// Construct adapter with listeners // Construct adapter with listeners
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList()); mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
@@ -259,8 +231,18 @@ public class FileSelectActivity extends StylishActivity implements
// Delete flickering for kitkat <= // Delete flickering for kitkat <=
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
overridePendingTransition(0, 0); 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) { } catch (Exception e) {
// Ignore exception Log.e(TAG, "Can't launch PasswordActivity", e);
} }
} }
@@ -452,33 +434,31 @@ public class FileSelectActivity extends StylishActivity implements
@Override @Override
public void onFileItemOpenListener(int itemPosition) { public void onFileItemOpenListener(int itemPosition) {
new OpenFileHistoryAsyncTask(new OpenFileHistoryAsyncTask.AfterOpenFileHistoryListener() { new OpenFileHistoryAsyncTask((fileName, keyFile) -> {
@Override // TODO ENCAPSULATE
public void afterOpenFile(String fileName, String keyFile) { try {
try { AssistStructure assistStructure = null;
AssistStructure assistStructure = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { assistStructure = autofillHelper.getAssistStructure();
assistStructure = autofillHelper.getAssistStructure(); if (assistStructure != null) {
if (assistStructure != null) { PasswordActivity.launch(FileSelectActivity.this,
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile, assistStructure);
fileName, keyFile, assistStructure);
}
}
if (assistStructure == null) {
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
} }
} catch (ContentFileNotFoundException e) { }
Toast.makeText(FileSelectActivity.this, if (assistStructure == null) {
R.string.file_not_found_content, Toast.LENGTH_LONG) PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
.show(); }
} catch (FileNotFoundException e) { } catch (ContentFileNotFoundException e) {
Toast.makeText(FileSelectActivity.this, Toast.makeText(FileSelectActivity.this,
R.string.file_not_found, Toast.LENGTH_LONG) R.string.file_not_found_content, Toast.LENGTH_LONG)
.show(); .show();
} } catch (FileNotFoundException e) {
updateTitleFileListView(); Toast.makeText(FileSelectActivity.this,
} R.string.file_not_found, Toast.LENGTH_LONG)
}, fileHistory).execute(itemPosition); .show();
}
updateTitleFileListView();
}, fileHistory).execute(itemPosition);
} }
@Override @Override
@@ -492,13 +472,10 @@ public class FileSelectActivity extends StylishActivity implements
@Override @Override
public boolean onFileSelectClearListener(final FileSelectBean fileSelectBean) { public boolean onFileSelectClearListener(final FileSelectBean fileSelectBean) {
new DeleteFileHistoryAsyncTask(new DeleteFileHistoryAsyncTask.AfterDeleteFileHistoryListener() { new DeleteFileHistoryAsyncTask(() -> {
@Override fileHistory.deleteFile(fileSelectBean.getFileUri());
public void afterDeleteFile() { mAdapter.notifyDataSetChanged();
fileHistory.deleteFile(fileSelectBean.getFileUri()); updateTitleFileListView();
mAdapter.notifyDataSetChanged();
updateTitleFileListView();
}
}, fileHistory, mAdapter).execute(fileSelectBean); }, fileHistory, mAdapter).execute(fileSelectBean);
return true; return true;
} }
@@ -512,33 +489,23 @@ public class FileSelectActivity extends StylishActivity implements
} }
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data, keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
new KeyFileHelper.KeyFileCallback() { uri -> {
@Override if (uri != null) {
public void onKeyFileResultCallback(Uri uri) { if (PreferencesUtil.autoOpenSelectedFile(FileSelectActivity.this)) {
if (uri != null) { launchPasswordActivityWithPath(uri.toString());
String filename = uri.toString(); } else {
openFileNameView.setText(filename); openFileNameView.setText(uri.toString());
} }
} }
}); });
} }
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE) @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void showRationaleForExternalStorage(final PermissionRequest request) { void showRationaleForExternalStorage(final PermissionRequest request) {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setMessage(R.string.permission_external_storage_rationale_write_database) .setMessage(R.string.permission_external_storage_rationale_write_database)
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
@Override .setNegativeButton(R.string.cancel, (dialog, which) -> request.cancel())
public void onClick(DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
request.cancel();
}
})
.show(); .show();
} }

View File

@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.ContextMenu; import android.view.ContextMenu;
@@ -48,6 +49,8 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
private FileSelectClearListener fileSelectClearListener; private FileSelectClearListener fileSelectClearListener;
private FileInformationShowListener fileInformationShowListener; private FileInformationShowListener fileInformationShowListener;
private @ColorInt
int defaultColor;
private @ColorInt private @ColorInt
int warningColor; int warningColor;
@@ -60,16 +63,19 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
Resources.Theme theme = context.getTheme(); Resources.Theme theme = context.getTheme();
theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true); theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true);
warningColor = typedValue.data; warningColor = typedValue.data;
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
defaultColor = typedValue.data;
} }
@NonNull
@Override @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); View view = inflater.inflate(R.layout.file_row, parent, false);
return new FileSelectViewHolder(view); return new FileSelectViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(FileSelectViewHolder holder, int position) { public void onBindViewHolder(@NonNull FileSelectViewHolder holder, int position) {
FileSelectBean fileSelectBean = new FileSelectBean(context, listFiles.get(position)); FileSelectBean fileSelectBean = new FileSelectBean(context, listFiles.get(position));
// Context menu creation // Context menu creation
holder.fileContainer.setOnCreateContextMenuListener(new ContextMenuBuilder(fileSelectBean)); holder.fileContainer.setOnCreateContextMenuListener(new ContextMenuBuilder(fileSelectBean));
@@ -87,6 +93,10 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
holder.fileInformation.setColorFilter( holder.fileInformation.setColorFilter(
warningColor, warningColor,
android.graphics.PorterDuff.Mode.MULTIPLY); android.graphics.PorterDuff.Mode.MULTIPLY);
} else {
holder.fileInformation.setColorFilter(
defaultColor,
android.graphics.PorterDuff.Mode.MULTIPLY);
} }
// Click on information // Click on information
if (fileInformationShowListener != null) if (fileInformationShowListener != null)
@@ -154,7 +164,7 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
private FileSelectBean fileSelectBean; private FileSelectBean fileSelectBean;
public ContextMenuBuilder(FileSelectBean fileSelectBean) { ContextMenuBuilder(FileSelectBean fileSelectBean) {
this.fileSelectBean = fileSelectBean; this.fileSelectBean = fileSelectBean;
} }

View File

@@ -31,12 +31,16 @@ public class FileSelectBean implements Serializable {
private static final String EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents"; private static final String EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents";
private String fileName = ""; private String fileName;
private Uri fileUri; private Uri fileUri;
private Date lastModification = new Date(); private Date lastModification;
private long size = 0; private long size;
public FileSelectBean(Context context, String pathFile) { public FileSelectBean(Context context, String pathFile) {
fileName = "";
lastModification = new Date();
size = 0;
fileUri = Uri.parse(pathFile); fileUri = Uri.parse(pathFile);
if (EXTERNAL_STORAGE_AUTHORITY.equals(fileUri.getAuthority())) { if (EXTERNAL_STORAGE_AUTHORITY.equals(fileUri.getAuthority())) {
DocumentFile file = DocumentFile.fromSingleUri(context, fileUri); DocumentFile file = DocumentFile.fromSingleUri(context, fileUri);
@@ -49,6 +53,10 @@ public class FileSelectBean implements Serializable {
fileName = file.getName(); fileName = file.getName();
lastModification = new Date(file.lastModified()); lastModification = new Date(file.lastModified());
} }
if (fileName == null || fileName.isEmpty()) {
fileName = fileUri.getPath();
}
} }
public boolean notFound() { public boolean notFound() {

View File

@@ -17,29 +17,35 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.view; package com.keepassdroid.fileselect;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import com.keepassdroid.compat.ContentResolverCompat; import com.keepassdroid.compat.ContentResolverCompat;
import com.keepassdroid.compat.StorageAF; import com.keepassdroid.compat.StorageAF;
import com.keepassdroid.fileselect.BrowserDialog; import com.keepassdroid.fileselect.BrowserDialog;
import com.keepassdroid.intents.Intents;
import com.keepassdroid.utils.Interaction; import com.keepassdroid.utils.Interaction;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
import javax.annotation.Nullable;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
public class KeyFileHelper { 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 OPEN_DOC = 25845;
private static final int FILE_BROWSE = 25645; private static final int FILE_BROWSE = 25645;
@@ -66,34 +72,53 @@ public class KeyFileHelper {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (StorageAF.useStorageFramework(activity)) { try {
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT); if (StorageAF.useStorageFramework(activity)) {
i.addCategory(Intent.CATEGORY_OPENABLE); openActivityWithActionOpenDocument();
i.setType("*/*"); } else {
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION| openActivityWithActionGetContent();
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());
} }
} 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() { public OpenFileOnClickViewListener getOpenFileOnClickViewListener() {
return new OpenFileOnClickViewListener(null); return new OpenFileOnClickViewListener(null);
} }
@@ -102,49 +127,46 @@ public class KeyFileHelper {
return new OpenFileOnClickViewListener(dataUri); return new OpenFileOnClickViewListener(dataUri);
} }
private void lookForOpenIntentsFilePicker(Uri dataUri) { private boolean lookForOpenIntentsFilePicker(@Nullable Uri dataUri) {
if (Interaction.isIntentAvailable(activity, Intents.OPEN_INTENTS_FILE_BROWSE)) { boolean showBrowser = false;
Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE); try {
if (Interaction.isIntentAvailable(activity, OPEN_INTENTS_FILE_BROWSE)) {
// Get file path parent if possible Intent i = new Intent(OPEN_INTENTS_FILE_BROWSE);
try { // Get file path parent if possible
if (dataUri != null if (dataUri != null
&& dataUri.toString().length() > 0 && dataUri.toString().length() > 0
&& dataUri.getScheme().equals("file")) { && dataUri.getScheme().equals("file")) {
i.setData(dataUri); 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 { } else {
Log.w(getClass().getName(), "Unable to read the URI"); 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) if(fragment != null)
fragment.startActivityForResult(i, FILE_BROWSE); fragment.startActivityForResult(i, FILE_BROWSE);
else else
activity.startActivityForResult(i, FILE_BROWSE); activity.startActivityForResult(i, FILE_BROWSE);
} catch (ActivityNotFoundException e) { } else {
showBrowserDialog(); showBrowser = true;
} }
} else { } catch (Exception e) {
showBrowserDialog(); 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() { private void showBrowserDialog() {
BrowserDialog browserDialog = new BrowserDialog(activity); try {
browserDialog.show(); 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);
}
} }
/** /**

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.view; package com.keepassdroid.fingerprint;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;

View File

@@ -30,6 +30,7 @@ import android.support.annotation.RequiresApi;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal; import android.support.v4.os.CancellationSignal;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.security.KeyStore; import java.security.KeyStore;
@@ -47,7 +48,9 @@ import javax.crypto.spec.IvParameterSpec;
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
public class FingerPrintHelper { 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 FingerprintManagerCompat fingerprintManager;
private KeyStore keyStore = null; private KeyStore keyStore = null;
@@ -65,8 +68,7 @@ public class FingerPrintHelper {
this.authenticationCallback = authenticationCallback; this.authenticationCallback = authenticationCallback;
} }
public void startListening() { public synchronized void startListening() {
// starts listening for fingerprints with the initialised crypto object // starts listening for fingerprints with the initialised crypto object
cancellationSignal = new CancellationSignal(); cancellationSignal = new CancellationSignal();
fingerprintManager.authenticate( fingerprintManager.authenticate(
@@ -77,7 +79,7 @@ public class FingerPrintHelper {
null); null);
} }
public void stopListening() { public synchronized void stopListening() {
if (!isFingerprintInitialized(false)) { if (!isFingerprintInitialized(false)) {
return; return;
} }
@@ -113,6 +115,7 @@ public class FingerPrintHelper {
this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher); this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
setInitOk(true); setInitOk(true);
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to initialize the keystore", e);
setInitOk(false); setInitOk(false);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
@@ -142,6 +145,8 @@ public class FingerPrintHelper {
return; return;
} }
try { try {
stopListening();
createNewKeyIfNeeded(false); // no need to keep deleting existing keys createNewKeyIfNeeded(false); // no need to keep deleting existing keys
keyStore.load(null); keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null); final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
@@ -149,10 +154,13 @@ public class FingerPrintHelper {
startListening(); startListening();
} catch (final UnrecoverableKeyException unrecoverableKeyException) { } catch (final UnrecoverableKeyException unrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException);
deleteEntryKey(); deleteEntryKey();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) { } catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException);
fingerPrintCallback.onInvalidKeyException(invalidKeyException); fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to initialize encrypt data", e);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
} }
@@ -164,14 +172,15 @@ public class FingerPrintHelper {
try { try {
// actual do encryption here // actual do encryption here
byte[] encrypted = cipher.doFinal(value.getBytes()); 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 // passes updated iv spec on to callback so this can be stored for decryption
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class); 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); fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to encrypt data", e);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
} }
@@ -181,21 +190,26 @@ public class FingerPrintHelper {
return; return;
} }
try { try {
stopListening();
createNewKeyIfNeeded(false); createNewKeyIfNeeded(false);
keyStore.load(null); keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null); final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
// important to restore spec here that was used for decryption // 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); final IvParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.init(Cipher.DECRYPT_MODE, key, spec);
startListening(); startListening();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final UnrecoverableKeyException unrecoverableKeyException) { } catch (final UnrecoverableKeyException unrecoverableKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException);
deleteEntryKey(); deleteEntryKey();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException);
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to initialize decrypt data", e);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
} }
@@ -206,15 +220,17 @@ public class FingerPrintHelper {
} }
try { try {
// actual decryption here // 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); byte[] decrypted = cipher.doFinal(encrypted);
final String decryptedString = new String(decrypted); final String decryptedString = new String(decrypted);
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */); //final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
fingerPrintCallback.handleDecryptedResult(decryptedString); fingerPrintCallback.handleDecryptedResult(decryptedString);
} catch (final BadPaddingException badPaddingException) { } catch (final BadPaddingException badPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException);
fingerPrintCallback.onInvalidKeyException(badPaddingException); fingerPrintCallback.onInvalidKeyException(badPaddingException);
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to decrypt data", e);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
} }
@@ -249,6 +265,7 @@ public class FingerPrintHelper {
keyGenerator.generateKey(); keyGenerator.generateKey();
} }
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Unable to create a key in keystore", e);
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }
} }
@@ -262,6 +279,7 @@ public class FingerPrintHelper {
| NoSuchAlgorithmException | NoSuchAlgorithmException
| IOException | IOException
| NullPointerException e) { | NullPointerException e) {
Log.e(TAG, "Unable to delete entry key in keystore", e);
if (fingerPrintCallback != null) if (fingerPrintCallback != null)
fingerPrintCallback.onFingerPrintException(e); fingerPrintCallback.onFingerPrintException(e);
} }

View File

@@ -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();
}
}

View 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();
}
}

View File

@@ -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);
}
}

View File

@@ -1,4 +1,4 @@
package com.keepassdroid.view; package com.keepassdroid.password;
import android.content.Context; import android.content.Context;

View File

@@ -22,8 +22,6 @@ package com.keepassdroid.password;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.assist.AssistStructure; import android.app.assist.AssistStructure;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@@ -47,11 +45,11 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.keepassdroid.activities.GroupActivity; import com.keepassdroid.activities.GroupActivity;
import com.keepassdroid.activities.LockingActivity;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.autofill.AutofillHelper; import com.keepassdroid.autofill.AutofillHelper;
import com.keepassdroid.compat.BackupManagerCompat; import com.keepassdroid.compat.BackupManagerCompat;
@@ -60,7 +58,7 @@ import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.edit.LoadDB; import com.keepassdroid.database.edit.LoadDB;
import com.keepassdroid.database.edit.OnFinish; 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.FingerPrintAnimatedVector;
import com.keepassdroid.fingerprint.FingerPrintHelper; import com.keepassdroid.fingerprint.FingerPrintHelper;
import com.keepassdroid.settings.PreferencesUtil; import com.keepassdroid.settings.PreferencesUtil;
@@ -69,8 +67,8 @@ import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.MenuUtil; import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.view.FingerPrintDialog; import com.keepassdroid.fingerprint.FingerPrintDialog;
import com.keepassdroid.view.KeyFileHelper; import com.keepassdroid.fileselect.KeyFileHelper;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import java.io.File; import java.io.File;
@@ -91,8 +89,9 @@ import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.STORE_MODE;
public class PasswordActivity extends StylishActivity public class PasswordActivity extends StylishActivity
implements FingerPrintHelper.FingerPrintCallback, UriIntentInitTaskCallback { implements FingerPrintHelper.FingerPrintCallback, UriIntentInitTaskCallback {
private static final String TAG = PasswordActivity.class.getName();
public static final String KEY_DEFAULT_FILENAME = "defaultFileName"; 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_PASSWORD = "password";
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately"; private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
@@ -204,19 +203,16 @@ public class PasswordActivity extends StylishActivity
boolean keyFileResult = false; boolean keyFileResult = false;
if (keyFileHelper != null) { if (keyFileHelper != null) {
keyFileResult = keyFileHelper.onActivityResultCallback(requestCode, resultCode, data, keyFileResult = keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
new KeyFileHelper.KeyFileCallback() { uri -> {
@Override if (uri != null) {
public void onKeyFileResultCallback(Uri uri) { populateKeyFileTextView(uri.toString());
if (uri != null) {
populateKeyFileTextView(uri.toString());
}
} }
}); });
} }
if (!keyFileResult) { if (!keyFileResult) {
// this block if not a key file response // this block if not a key file response
switch (resultCode) { switch (resultCode) {
case RESULT_EXIT_LOCK: case LockingActivity.RESULT_EXIT_LOCK:
case Activity.RESULT_CANCELED: case Activity.RESULT_CANCELED:
setEmptyViews(); setEmptyViews();
App.getDB().clear(); App.getDB().clear();
@@ -237,20 +233,20 @@ public class PasswordActivity extends StylishActivity
setContentView(R.layout.password); setContentView(R.layout.password);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name)); toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
confirmButtonView = (Button) findViewById(R.id.pass_ok); confirmButtonView = findViewById(R.id.pass_ok);
filenameView = (TextView) findViewById(R.id.filename); filenameView = findViewById(R.id.filename);
passwordView = (EditText) findViewById(R.id.password); passwordView = findViewById(R.id.password);
keyFileView = (EditText) findViewById(R.id.pass_keyfile); keyFileView = findViewById(R.id.pass_keyfile);
checkboxPasswordView = (CompoundButton) findViewById(R.id.password_checkbox); checkboxPasswordView = findViewById(R.id.password_checkbox);
checkboxKeyfileView = (CompoundButton) findViewById(R.id.keyfile_checkox); checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
checkboxDefaultDatabaseView = (CompoundButton) findViewById(R.id.default_database); checkboxDefaultDatabaseView = findViewById(R.id.default_database);
View browseView = findViewById(R.id.browse_button); View browseView = findViewById(R.id.browse_button);
keyFileHelper = new KeyFileHelper(PasswordActivity.this); keyFileHelper = new KeyFileHelper(PasswordActivity.this);
@@ -265,7 +261,7 @@ public class PasswordActivity extends StylishActivity
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
if (!editable.toString().isEmpty() && !checkboxKeyfileView.isChecked()) if (!editable.toString().isEmpty() && !checkboxPasswordView.isChecked())
checkboxPasswordView.setChecked(true); checkboxPasswordView.setChecked(true);
} }
}); });
@@ -288,10 +284,10 @@ public class PasswordActivity extends StylishActivity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerprintContainerView = findViewById(R.id.fingerprint_container); fingerprintContainerView = findViewById(R.id.fingerprint_container);
fingerprintTextView = (TextView) findViewById(R.id.fingerprint_label); fingerprintTextView = findViewById(R.id.fingerprint_label);
initForFingerprint(); initForFingerprint();
fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this, fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this,
(ImageView) findViewById(R.id.fingerprint_image)); findViewById(R.id.fingerprint_image));
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -446,7 +442,7 @@ public class PasswordActivity extends StylishActivity
if ( !fingerprintMustBeConfigured ) { if ( !fingerprintMustBeConfigured ) {
final boolean validInput = s.length() > 0; final boolean validInput = s.length() > 0;
// encrypt or decrypt mode based on how much input or not // 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) if (validInput)
toggleFingerprintMode(STORE_MODE); toggleFingerprintMode(STORE_MODE);
else else
@@ -461,22 +457,30 @@ public class PasswordActivity extends StylishActivity
public void onAuthenticationError( public void onAuthenticationError(
final int errorCode, final int errorCode,
final CharSequence errString) { 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 @Override
public void onAuthenticationHelp( public void onAuthenticationHelp(
final int helpCode, final int helpCode,
final CharSequence helpString) { final CharSequence helpString) {
Log.w(TAG, "Fingerprint authentication help. Code : " + helpCode + " Help : " + helpString);
showError(helpString); showError(helpString);
reInitWithSameFingerprintMode(); setFingerPrintView(helpString.toString(), true);
fingerprintTextView.setText(helpString); fingerprintTextView.setText(helpString);
} }
@Override @Override
public void onAuthenticationFailed() { public void onAuthenticationFailed() {
Log.e(TAG, "Fingerprint authentication failed, fingerprint not recognized");
showError(R.string.fingerprint_not_recognized); showError(R.string.fingerprint_not_recognized);
reInitWithSameFingerprintMode();
} }
@Override @Override
@@ -510,7 +514,7 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
private void initEncryptData() { private void initEncryptData() {
setFingerPrintTextView(R.string.store_with_fingerprint); setFingerPrintView(R.string.store_with_fingerprint);
fingerPrintMode = STORE_MODE; fingerPrintMode = STORE_MODE;
if (fingerPrintHelper != null) if (fingerPrintHelper != null)
fingerPrintHelper.initEncryptData(); fingerPrintHelper.initEncryptData();
@@ -518,7 +522,7 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
private void initDecryptData() { private void initDecryptData() {
setFingerPrintTextView(R.string.scanning_fingerprint); setFingerPrintView(R.string.scanning_fingerprint);
fingerPrintMode = OPEN_MODE; fingerPrintMode = OPEN_MODE;
if (fingerPrintHelper != null) { if (fingerPrintHelper != null) {
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null); final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
@@ -531,12 +535,12 @@ public class PasswordActivity extends StylishActivity
private synchronized void toggleFingerprintMode(final FingerPrintHelper.Mode newMode) { private synchronized void toggleFingerprintMode(final FingerPrintHelper.Mode newMode) {
if( !newMode.equals(fingerPrintMode) ) { if( !newMode.equals(fingerPrintMode) ) {
fingerPrintMode = newMode; fingerPrintMode = newMode;
reInitWithSameFingerprintMode(); reInitWithFingerprintMode();
} }
} }
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
private synchronized void reInitWithSameFingerprintMode() { private synchronized void reInitWithFingerprintMode() {
switch (fingerPrintMode) { switch (fingerPrintMode) {
case STORE_MODE: case STORE_MODE:
initEncryptData(); initEncryptData();
@@ -551,8 +555,6 @@ public class PasswordActivity extends StylishActivity
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (fingerPrintAnimatedVector != null) { if (fingerPrintAnimatedVector != null) {
fingerPrintAnimatedVector.stopScan(); fingerPrintAnimatedVector.stopScan();
@@ -563,32 +565,32 @@ public class PasswordActivity extends StylishActivity
fingerPrintHelper.stopListening(); fingerPrintHelper.stopListening();
} }
} }
super.onPause();
} }
private void setFingerPrintVisibility(final int vis) { private void setFingerPrintVisibility(final int vis) {
runOnUiThread(new Runnable() { runOnUiThread(() -> fingerprintContainerView.setVisibility(vis));
@Override
public void run() {
fingerprintContainerView.setVisibility(vis);
}
});
} }
private void setFingerPrintTextView(final int textId) { private void setFingerPrintView(final int textId) {
runOnUiThread(new Runnable() { setFingerPrintView(textId, false);
@Override
public void run() {
fingerprintTextView.setText(textId);
}
});
} }
private void setFingerPrintAlphaImageView(final float alpha) { private void setFingerPrintView(final CharSequence text) {
runOnUiThread(new Runnable() { setFingerPrintView(text, false);
@Override }
public void run() {
fingerprintContainerView.setAlpha(alpha); 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);
}); });
} }
@@ -603,28 +605,23 @@ public class PasswordActivity extends StylishActivity
// fingerprint is available but not configured show icon but in disabled state with some information // fingerprint is available but not configured show icon but in disabled state with some information
else { else {
// show explanations // show explanations
fingerprintContainerView.setOnClickListener(new View.OnClickListener() { fingerprintContainerView.setOnClickListener(view -> {
@Override FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
public void onClick(View view) { fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
}
}); });
setFingerPrintVisibility(View.VISIBLE); setFingerPrintVisibility(View.VISIBLE);
if (!fingerPrintHelper.hasEnrolledFingerprints()) { if (!fingerPrintHelper.hasEnrolledFingerprints()) {
setFingerPrintAlphaImageView(0.6f);
// This happens when no fingerprints are registered. Listening won't start // 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 // finally fingerprint available and configured so we can use it
else { else {
fingerprintMustBeConfigured = false; fingerprintMustBeConfigured = false;
setFingerPrintAlphaImageView(1f);
// fingerprint available but no stored password found yet for this DB so show info don't listen // fingerprint available but no stored password found yet for this DB so show info don't listen
if (!prefsNoBackup.contains(getPreferenceKeyValue())) { if (!prefsNoBackup.contains(getPreferenceKeyValue())) {
setFingerPrintTextView(R.string.no_password_stored); setFingerPrintView(R.string.no_password_stored);
// listen for encryption // listen for encryption
initEncryptData(); initEncryptData();
} }
@@ -640,7 +637,7 @@ public class PasswordActivity extends StylishActivity
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
private void removePrefsNoBackupKeys() { private void removePrefsNoBackupKey() {
prefsNoBackup.edit() prefsNoBackup.edit()
.remove(getPreferenceKeyValue()) .remove(getPreferenceKeyValue())
.remove(getPreferenceKeyIvSpec()) .remove(getPreferenceKeyIvSpec())
@@ -656,7 +653,7 @@ public class PasswordActivity extends StylishActivity
.putString(getPreferenceKeyIvSpec(), ivSpec) .putString(getPreferenceKeyIvSpec(), ivSpec)
.apply(); .apply();
verifyAllViewsAndLoadDatabase(); verifyAllViewsAndLoadDatabase();
setFingerPrintTextView(R.string.encrypted_value_stored); setFingerPrintView(R.string.encrypted_value_stored);
} }
@Override @Override
@@ -668,36 +665,31 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
@Override @Override
public void onInvalidKeyException(Exception e) { public void onInvalidKeyException(Exception e) {
showError(R.string.fingerprint_invalid_key); showError(getString(R.string.fingerprint_invalid_key));
removePrefsNoBackupKeys(); deleteEntryKey();
e.printStackTrace();
reInitWithSameFingerprintMode(); // restarts listening
} }
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
@Override @Override
public void onFingerPrintException(Exception e) { public void onFingerPrintException(Exception e) {
showError(R.string.fingerprint_error); showError(getString(R.string.fingerprint_error, e.getMessage()));
e.printStackTrace(); setFingerPrintView(e.getLocalizedMessage(), true);
reInitWithSameFingerprintMode(); }
@RequiresApi(api = Build.VERSION_CODES.M)
private void deleteEntryKey() {
fingerPrintHelper.deleteEntryKey();
removePrefsNoBackupKey();
fingerPrintMode = NOT_CONFIGURED_MODE;
checkFingerprintAvailability();
} }
private void showError(final int messageId) { private void showError(final int messageId) {
runOnUiThread(new Runnable() { showError(getString(messageId));
@Override
public void run() {
Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_SHORT).show();
}
});
} }
private void showError(final CharSequence message) { private void showError(final CharSequence message) {
runOnUiThread(new Runnable() { runOnUiThread(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
@Override
public void run() {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
});
} }
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener { private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
@@ -787,10 +779,7 @@ public class PasswordActivity extends StylishActivity
break; break;
case R.id.menu_fingerprint_remove_key: case R.id.menu_fingerprint_remove_key:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerPrintHelper.deleteEntryKey(); deleteEntryKey();
removePrefsNoBackupKeys();
fingerPrintMode = NOT_CONFIGURED_MODE;
checkFingerprintAvailability();
} }
break; break;
default: default:
@@ -827,25 +816,13 @@ public class PasswordActivity extends StylishActivity
// Recheck fingerprint if error // Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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 // Stay with the same mode
reInitWithSameFingerprintMode(); reInitWithFingerprintMode();
} }
if (db.passwordEncodingError) { if (db.passwordEncodingError) {
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper(); PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
dialog.show(PasswordActivity.this, new OnClickListener() { dialog.show(PasswordActivity.this, (dialog1, which) -> launchGroupActivity());
@Override
public void onClick(
DialogInterface dialog,
int which) {
launchGroupActivity();
}
});
} else if (mSuccess) { } else if (mSuccess) {
launchGroupActivity(); launchGroupActivity();
} else { } else {
@@ -950,18 +927,8 @@ public class PasswordActivity extends StylishActivity
void showRationaleForExternalStorage(final PermissionRequest request) { void showRationaleForExternalStorage(final PermissionRequest request) {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setMessage(R.string.permission_external_storage_rationale_read_database) .setMessage(R.string.permission_external_storage_rationale_read_database)
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
@Override .setNegativeButton(R.string.cancel, (dialog, which) -> request.cancel())
public void onClick(DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
request.cancel();
}
})
.show(); .show();
} }

View File

@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R;
public class PasswordGenerator { public class PasswordGenerator {
private static final String UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 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 DIGIT_CHARS = "0123456789";
private static final String MINUS_CHAR = "-"; private static final String MINUS_CHAR = "-";
private static final String UNDERLINE_CHAR = "_"; private static final String UNDERLINE_CHAR = "_";
@@ -35,6 +35,19 @@ public class PasswordGenerator {
private static final String SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`"; private static final String SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`";
private static final String BRACKET_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; private Context cxt;
@@ -42,22 +55,48 @@ public class PasswordGenerator {
this.cxt = cxt; 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 // Desired password length is 0 or less
if (length <= 0) { if (length <= 0) {
throw new IllegalArgumentException(cxt.getString(R.string.error_wrong_length)); throw new IllegalArgumentException(cxt.getString(R.string.error_wrong_length));
} }
// No option has been checked // 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)); 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(); int size = characterSet.length();
StringBuffer buffer = new StringBuffer(); StringBuilder buffer = new StringBuilder();
SecureRandom random = new SecureRandom(); // use more secure variant of Random! SecureRandom random = new SecureRandom(); // use more secure variant of Random!
if (size > 0) { if (size > 0) {
@@ -69,8 +108,16 @@ public class PasswordGenerator {
return buffer.toString(); return buffer.toString();
} }
public String getCharacterSet(boolean upperCase, boolean lowerCase, boolean digits, boolean minus, boolean underline, boolean space, boolean specials, boolean brackets) { private String getCharacterSet(boolean upperCase,
StringBuffer charSet = new StringBuffer(); boolean lowerCase,
boolean digits,
boolean minus,
boolean underline,
boolean space,
boolean specials,
boolean brackets,
boolean extended) {
StringBuilder charSet = new StringBuilder();
if (upperCase) { if (upperCase) {
charSet.append(UPPERCASE_CHARS); charSet.append(UPPERCASE_CHARS);
@@ -103,7 +150,11 @@ public class PasswordGenerator {
if (brackets) { if (brackets) {
charSet.append(BRACKET_CHARS); charSet.append(BRACKET_CHARS);
} }
if (extended) {
charSet.append(extendedChars());
}
return charSet.toString(); return charSet.toString();
} }
} }

View File

@@ -66,8 +66,8 @@ public class SearchDbHelper {
Log.d("SearchDbHelper", "Tried to search with unknown db"); Log.d("SearchDbHelper", "Tried to search with unknown db");
return null; return null;
} }
group.name = mCtx.getString(R.string.search_results); group.setName(mCtx.getString(R.string.search_results));
group.childEntries = new ArrayList<PwEntry>(); group.setEntries(new ArrayList<>());
// Search all entries // Search all entries
Locale loc = Locale.getDefault(); Locale loc = Locale.getDefault();
@@ -83,11 +83,11 @@ public class SearchDbHelper {
PwGroup top = worklist.remove(); PwGroup top = worklist.remove();
if (pm.isGroupSearchable(top, isOmitBackup)) { if (pm.isGroupSearchable(top, isOmitBackup)) {
for (PwEntry entry : top.childEntries) { for (PwEntry entry : top.getChildEntries()) {
processEntries(entry, group.childEntries, qStr, loc); processEntries(entry, group.getChildEntries(), qStr, loc);
} }
for (PwGroup childGroup : top.childGroups) { for (PwGroup childGroup : top.getChildGroups()) {
if (childGroup != null) { if (childGroup != null) {
worklist.add(childGroup); worklist.add(childGroup);
} }

View File

@@ -19,6 +19,7 @@
*/ */
package com.keepassdroid.settings; package com.keepassdroid.settings;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Resources; import android.content.res.Resources;
@@ -39,7 +40,7 @@ import android.view.autofill.AutofillManager;
import android.widget.Toast; import android.widget.Toast;
import com.keepassdroid.database.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.fragments.UnavailableFeatureDialogFragment; import com.keepassdroid.dialogs.UnavailableFeatureDialogFragment;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.database.PwEncryptionAlgorithm; import com.keepassdroid.database.PwEncryptionAlgorithm;
import com.keepassdroid.fingerprint.FingerPrintHelper; import com.keepassdroid.fingerprint.FingerPrintHelper;
@@ -130,16 +131,13 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (!fingerprintSupported) { if (!fingerprintSupported) {
// False if under Marshmallow // False if under Marshmallow
fingerprintEnablePreference.setChecked(false); fingerprintEnablePreference.setChecked(false);
fingerprintEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { fingerprintEnablePreference.setOnPreferenceClickListener(preference -> {
@Override FragmentManager fragmentManager = getFragmentManager();
public boolean onPreferenceClick(Preference preference) { assert fragmentManager != null;
FragmentManager fragmentManager = getFragmentManager(); ((SwitchPreference) preference).setChecked(false);
assert fragmentManager != null; UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
((SwitchPreference) preference).setChecked(false); .show(getFragmentManager(), "unavailableFeatureDialog");
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) return false;
.show(getFragmentManager(), "unavailableFeatureDialog");
return false;
}
}); });
} }
@@ -147,45 +145,39 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (!fingerprintSupported) { if (!fingerprintSupported) {
deleteKeysFingerprints.setEnabled(false); deleteKeysFingerprints.setEnabled(false);
} else { } else {
deleteKeysFingerprints.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { deleteKeysFingerprints.setOnPreferenceClickListener(preference -> {
@Override new AlertDialog.Builder(getContext())
public boolean onPreferenceClick(Preference preference) { .setMessage(getResources().getString(R.string.fingerprint_delete_all_warning))
new AlertDialog.Builder(getContext()) .setIcon(getResources().getDrawable(
.setMessage(getResources().getString(R.string.fingerprint_delete_all_warning)) android.R.drawable.ic_dialog_alert))
.setIcon(getResources().getDrawable( .setPositiveButton(
android.R.drawable.ic_dialog_alert)) getResources().getString(android.R.string.yes),
.setPositiveButton( new DialogInterface.OnClickListener() {
getResources().getString(android.R.string.yes), @RequiresApi(api = Build.VERSION_CODES.M)
new DialogInterface.OnClickListener() { @Override
@RequiresApi(api = Build.VERSION_CODES.M) public void onClick(DialogInterface dialog,
@Override int which) {
public void onClick(DialogInterface dialog, FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints(
int which) { getContext(),
FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints( new FingerPrintHelper.FingerPrintErrorCallback() {
getContext(), @Override
new FingerPrintHelper.FingerPrintErrorCallback() { public void onInvalidKeyException(Exception e) {}
@Override
public void onInvalidKeyException(Exception e) {
}
@Override @Override
public void onFingerPrintException(Exception e) { public void onFingerPrintException(Exception e) {
Toast.makeText(getContext(), R.string.fingerprint_error, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(),
} getString(R.string.fingerprint_error, e.getLocalizedMessage()),
}); Toast.LENGTH_SHORT).show();
PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext()); }
} });
}) PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext());
.setNegativeButton( }
getResources().getString(android.R.string.no), })
new DialogInterface.OnClickListener() { .setNegativeButton(
@Override getResources().getString(android.R.string.no),
public void onClick(DialogInterface dialog, (dialog, which) -> {
int which) { }).show();
} return false;
}).show();
return false;
}
}); });
} }
break; break;
@@ -204,7 +196,14 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (((SwitchPreference) preference).isChecked()) { 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 { } else {
disableService(); disableService();
} }
@@ -221,11 +220,11 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
private void startEnableService() { private void startEnableService() throws ActivityNotFoundException{
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) { if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE); Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
intent.setData(Uri.parse("package:com.example.android.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); startActivityForResult(intent, REQUEST_CODE_AUTOFILL);
} else { } else {
Log.d(getClass().getName(), "Sample service already enabled."); Log.d(getClass().getName(), "Sample service already enabled.");

View File

@@ -125,4 +125,16 @@ public class PreferencesUtil {
return prefs.getBoolean(ctx.getString(R.string.monospace_font_fields_enable_key), return prefs.getBoolean(ctx.getString(R.string.monospace_font_fields_enable_key),
ctx.getResources().getBoolean(R.bool.monospace_font_fields_enable_default)); 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));
}
} }

View File

@@ -19,12 +19,14 @@
*/ */
package com.keepassdroid.settings; package com.keepassdroid.settings;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.keepassdroid.activities.LockingActivity; import com.keepassdroid.activities.LockingActivity;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.BackupManagerCompat; import com.keepassdroid.compat.BackupManagerCompat;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
@@ -37,15 +39,28 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
private Toolbar toolbar; private Toolbar toolbar;
@Override public static void launch(Activity activity) {
protected void onResume() { Intent i = new Intent(activity, SettingsActivity.class);
activity.startActivity(i);
// Clear the shutdown flag
App.clearShutdown();
super.onResume();
} }
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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -59,7 +74,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
if (savedInstanceState == null) { if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new MainPreferenceFragment()) .add(R.id.fragment_container, retrieveMainFragment())
.commit(); .commit();
} }
@@ -80,7 +95,6 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
@Override @Override
protected void onStop() { protected void onStop() {
backupManager.dataChanged(); backupManager.dataChanged();
super.onStop(); super.onStop();
} }

View File

@@ -1,6 +1,6 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2018 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePass DX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * 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 * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/package com.keepassdroid.intents; */
package com.keepassdroid.settings;
public class Intents { import android.support.v4.app.Fragment;
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";
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);
}
} }

View 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();
}
}

View File

@@ -1,4 +1,4 @@
package com.keepassdroid.timers; package com.keepassdroid.timeout;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -9,24 +9,21 @@ import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.keepassdroid.intents.Intents;
import com.keepassdroid.services.TimeoutService;
public class Timeout { public class Timeout {
public static final String TIMEOUT = "com.keepassdroid.timeout";
private static final int REQUEST_ID = 0; private static final int REQUEST_ID = 0;
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
private static String TAG = "KeePass Timeout"; private static String TAG = "KeePass Timeout";
private static PendingIntent buildIntent(Context ctx) { private static PendingIntent buildIntent(Context ctx) {
Intent intent = new Intent(Intents.TIMEOUT); Intent intent = new Intent(TIMEOUT);
PendingIntent sender = PendingIntent.getBroadcast(ctx, REQUEST_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT); return PendingIntent.getBroadcast(ctx, REQUEST_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return sender;
} }
public static void start(Context ctx) { public static void start(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
String sTimeout = prefs.getString(ctx.getString(R.string.app_timeout_key), ctx.getString(R.string.clipboard_timeout_default)); 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); AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
Log.d(TAG, "Timeout start"); 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) { public static void cancel(Context ctx) {
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
Log.d(TAG, "Timeout cancel"); Log.d(TAG, "Timeout cancel");
am.cancel(buildIntent(ctx)); if (am != null) {
am.cancel(buildIntent(ctx));
ctx.stopService(new Intent(ctx, TimeoutService.class)); }
ctx.stopService(new Intent(ctx, TimeoutService.class));
} }
} }

View File

@@ -23,14 +23,15 @@ import android.app.Activity;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import com.keepassdroid.password.PasswordActivity; import com.keepassdroid.activities.LockingActivity;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.compat.EditorCompat; import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.timers.Timeout;
public class TimeoutHelper { public class TimeoutHelper {
private static final String TAG = "TimeoutHelper";
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
public static void recordTime(Activity act) { public static void recordTime(Activity act) {
@@ -48,7 +49,7 @@ public class TimeoutHelper {
} }
} }
public static void checkTime(Activity act) { public static boolean checkTime(Activity act) {
if ( App.getDB().Loaded() ) { if ( App.getDB().Loaded() ) {
Timeout.cancel(act); Timeout.cancel(act);
} }
@@ -60,10 +61,9 @@ public class TimeoutHelper {
long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), -1); long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), -1);
// The timeout never started // The timeout never started
if (timeout_start == -1) { 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)); String sTimeout = prefs.getString(act.getString(R.string.app_timeout_key), act.getString(R.string.clipboard_timeout_default));
long timeout; long timeout;
try { try {
@@ -74,20 +74,19 @@ public class TimeoutHelper {
// We are set to never timeout // We are set to never timeout
if (timeout == -1) { if (timeout == -1) {
return; return true;
} }
long diff = cur_time - timeout_start; long diff = cur_time - timeout_start;
if (diff >= timeout) { if (diff >= timeout) {
// We have timed out // We have timed out
App.setShutdown(act.getString(R.string.app_timeout)); 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();
}
}
} }

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.keepassdroid.services; package com.keepassdroid.timeout;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.Service; import android.app.Service;
@@ -30,7 +30,6 @@ import android.os.IBinder;
import android.util.Log; import android.util.Log;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.intents.Intents;
public class TimeoutService extends Service { public class TimeoutService extends Service {
private static final String TAG = "KeePassDroid Timer"; private static final String TAG = "KeePassDroid Timer";
@@ -44,42 +43,36 @@ public class TimeoutService extends Service {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if ( action != null && action.equals(Timeout.TIMEOUT) ) {
if ( action.equals(Intents.TIMEOUT) ) { timeout();
timeout(context);
} }
} }
}; };
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(Intents.TIMEOUT); filter.addAction(Timeout.TIMEOUT);
registerReceiver(mIntentReceiver, filter); registerReceiver(mIntentReceiver, filter);
} }
@Override @Override
public void onStart(Intent intent, int startId) { public void onStart(Intent intent, int startId) {
super.onStart(intent, startId); super.onStart(intent, startId);
Log.d(TAG, "Timeout service started"); Log.d(TAG, "Timeout service started");
} }
private void timeout(Context context) { private void timeout() {
Log.d(TAG, "Timeout"); Log.d(TAG, "Timeout");
App.setShutdown(); App.setShutdown();
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancelAll(); if (nm != null)
nm.cancelAll();
stopSelf(); stopSelf();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
Log.d(TAG, "Timeout service stopped"); Log.d(TAG, "Timeout service stopped");
unregisterReceiver(mIntentReceiver); unregisterReceiver(mIntentReceiver);
} }

View File

@@ -22,7 +22,8 @@ package com.keepassdroid.utils;
import android.net.Uri; import android.net.Uri;
import com.keepassdroid.database.PwDate; import com.keepassdroid.database.PwDate;
import com.keepassdroid.database.PwEntryV3;
import static com.keepassdroid.database.PwDate.DEFAULT_PWDATE;
public class EmptyUtils { public class EmptyUtils {
public static boolean isNullOrEmpty(String str) { public static boolean isNullOrEmpty(String str) {
@@ -34,7 +35,7 @@ public class EmptyUtils {
} }
public static boolean isNullOrEmpty(PwDate date) { 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) { public static boolean isNullOrEmpty(Uri uri) {

View File

@@ -7,11 +7,11 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R;
import com.keepassdroid.activities.AboutActivity; import com.keepassdroid.activities.AboutActivity;
import com.keepassdroid.settings.SettingsActivity; import com.keepassdroid.settings.SettingsActivity;
import com.keepassdroid.stylish.StylishActivity; import com.keepassdroid.stylish.StylishActivity;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R;
public class MenuUtil { public class MenuUtil {
@@ -37,13 +37,20 @@ public class MenuUtil {
} }
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) { public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
return onDefaultMenuOptionsItemSelected(activity, item, false);
}
/*
* @param checkLock Check the time lock before launch settings in LockingActivity
*/
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_donate: case R.id.menu_donate:
return onDonationItemSelected(activity); return onDonationItemSelected(activity);
case R.id.menu_app_settings: case R.id.menu_app_settings:
Intent i = new Intent(activity, SettingsActivity.class); // To avoid flickering when launch settings in a LockingActivity
activity.startActivity(i); SettingsActivity.launch(activity, checkLock);
return true; return true;
case R.id.menu_about: case R.id.menu_about:

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -30,7 +30,7 @@ import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.SearchParametersV4; import com.keepassdroid.database.SearchParametersV4;
public class SprEngineV4 extends SprEngine { public class SprEngineV4 {
private final int MAX_RECURSION_DEPTH = 12; private final int MAX_RECURSION_DEPTH = 12;
private final String STR_REF_START = "{REF:"; private final String STR_REF_START = "{REF:";
private final String STR_REF_END = "}"; 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) { public String compile(String text, PwEntry entry, PwDatabase database) {
SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, (PwEntryV4)entry); SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, (PwEntryV4)entry);

View File

@@ -24,36 +24,20 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; 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 android.widget.TextView;
import com.keepassdroid.database.exception.SamsungClipboardException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public class Util { 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 { public static void gotoUrl(Context context, String url) throws ActivityNotFoundException {
if ( url != null && url.length() > 0 ) { if ( url != null && url.length() > 0 ) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
@@ -73,9 +57,66 @@ public class Util {
} }
} }
public static void applyFontVisibilityToTextView(boolean applyMonospace, TextView textView) { private final static String stringToStrikeThrough = "0";
if (applyMonospace)
textView.setTypeface(Typeface.MONOSPACE); /**
* 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;
}
} }

View File

@@ -19,6 +19,7 @@
*/ */
package com.keepassdroid.view; package com.keepassdroid.view;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
@@ -36,15 +37,15 @@ import android.widget.RelativeLayout;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
public class ListNodesWithAddButtonView extends RelativeLayout { public class AddNodeButtonView extends RelativeLayout {
private enum State { private enum State {
OPEN, CLOSE OPEN, CLOSE
} }
private FloatingActionButton addButton; private FloatingActionButton addButtonView;
private View addEntry; private View addEntryView;
private View addGroup; private View addGroupView;
private boolean addEntryEnable; private boolean addEntryEnable;
private boolean addGroupEnable; private boolean addGroupEnable;
@@ -58,11 +59,11 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
private ViewMenuAnimation viewMenuAnimationAddGroup; private ViewMenuAnimation viewMenuAnimationAddGroup;
private long animationDuration; private long animationDuration;
public ListNodesWithAddButtonView(Context context) { public AddNodeButtonView(Context context) {
this(context, null); this(context, null);
} }
public ListNodesWithAddButtonView(Context context, AttributeSet attrs) { public AddNodeButtonView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
inflate(context); inflate(context);
} }
@@ -70,83 +71,90 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
protected void inflate(Context context) { protected void inflate(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null; assert inflater != null;
inflater.inflate(R.layout.list_nodes_with_add_button, this); inflater.inflate(R.layout.add_node_button, this);
addEntryEnable = true; addEntryEnable = true;
addGroupEnable = true; addGroupEnable = true;
addButton = (FloatingActionButton) findViewById(R.id.add_button); addButtonView = findViewById(R.id.add_button);
addEntry = findViewById(R.id.add_entry); addEntryView = findViewById(R.id.add_entry);
addGroup = findViewById(R.id.add_group); addGroupView = findViewById(R.id.add_group);
animationDuration = 300L; animationDuration = 300L;
viewButtonMenuAnimation = new AddButtonAnimation(addButton); viewButtonMenuAnimation = new AddButtonAnimation(addButtonView);
viewMenuAnimationAddEntry = new ViewMenuAnimation(addEntry, 0L, 150L); viewMenuAnimationAddEntry = new ViewMenuAnimation(addEntryView, 0L, 150L);
viewMenuAnimationAddGroup = new ViewMenuAnimation(addGroup, 150L, 0L); viewMenuAnimationAddGroup = new ViewMenuAnimation(addGroupView, 150L, 0L);
allowAction = true; allowAction = true;
state = State.CLOSE; state = State.CLOSE;
onAddButtonClickListener = new OnClickListener() { onAddButtonClickListener = v -> startGlobalAnimation();
@Override addButtonView.setOnClickListener(onAddButtonClickListener);
public void onClick(View v) {
if (allowAction && state.equals(State.CLOSE)) {
startGlobalAnimation();
}
}
};
addButton.setOnClickListener(onAddButtonClickListener);
onAddButtonVisibilityChangedListener = new FloatingActionButton.OnVisibilityChangedListener() { onAddButtonVisibilityChangedListener = new FloatingActionButton.OnVisibilityChangedListener() {
@Override @Override
public void onHidden(FloatingActionButton fab) { public void onHidden(FloatingActionButton fab) {
super.onHidden(fab); super.onHidden(fab);
addButton.setOnClickListener(null); addButtonView.setOnClickListener(null);
addButton.setClickable(false); addButtonView.setClickable(false);
} }
@Override @Override
public void onShown(FloatingActionButton fab) { public void onShown(FloatingActionButton fab) {
super.onShown(fab); super.onShown(fab);
addButton.setOnClickListener(onAddButtonClickListener); addButtonView.setOnClickListener(onAddButtonClickListener);
} }
}; };
}
// Hide when scroll @SuppressLint("ClickableViewAccessibility")
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.nodes_list); @Override
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 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 @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy); super.onScrolled(recyclerView, dx, dy);
if (state.equals(State.CLOSE)) { if (state.equals(State.CLOSE)) {
if (dy > 0 && addButton.getVisibility() == View.VISIBLE) { if (dy > 0 && addButtonView.getVisibility() == View.VISIBLE) {
hideButton(); hideButton();
} else if (dy < 0 && addButton.getVisibility() != View.VISIBLE) { } else if (dy < 0 && addButtonView.getVisibility() != View.VISIBLE) {
showButton(); showButton();
} }
} }
} }
}); };
} }
public void showButton() { public void showButton() {
addButton.show(onAddButtonVisibilityChangedListener); addButtonView.show(onAddButtonVisibilityChangedListener);
} }
public void hideButton() { public void hideButton() {
addButton.hide(onAddButtonVisibilityChangedListener); addButtonView.hide(onAddButtonVisibilityChangedListener);
} }
@Override /**
public boolean dispatchTouchEvent(MotionEvent ev) { * Start the animation to close the button
Rect viewRectG = new Rect(); */
getGlobalVisibleRect(viewRectG); public void closeButtonIfOpen() {
if (viewRectG.contains((int) ev.getRawX(), (int) ev.getRawY())) { if(state.equals(State.OPEN)) {
if(allowAction && state.equals(State.OPEN)) { startGlobalAnimation();
startGlobalAnimation();
}
} }
return super.dispatchTouchEvent(ev);
} }
/** /**
@@ -155,8 +163,8 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
*/ */
public void enableAddEntry(boolean enable) { public void enableAddEntry(boolean enable) {
this.addEntryEnable = enable; this.addEntryEnable = enable;
if (enable && addEntry != null && addEntry.getVisibility() != VISIBLE) if (enable && addEntryView != null && addEntryView.getVisibility() != VISIBLE)
addEntry.setVisibility(INVISIBLE); addEntryView.setVisibility(INVISIBLE);
} }
/** /**
@@ -165,28 +173,36 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
*/ */
public void enableAddGroup(boolean enable) { public void enableAddGroup(boolean enable) {
this.addGroupEnable = enable; this.addGroupEnable = enable;
if (enable && addGroup != null && addGroup.getVisibility() != VISIBLE) if (enable && addGroupView != null && addGroupView.getVisibility() != VISIBLE)
addGroup.setVisibility(INVISIBLE); addGroupView.setVisibility(INVISIBLE);
} }
public void setAddGroupClickListener(OnClickListener onClickListener) { public void setAddGroupClickListener(OnClickListener onClickListener) {
if (addEntryEnable) if (addGroupEnable)
addGroup.setOnClickListener(onClickListener); addGroupView.setOnClickListener(view -> {
onClickListener.onClick(view);
closeButtonIfOpen();
});
} }
public void setAddEntryClickListener(OnClickListener onClickListener) { public void setAddEntryClickListener(OnClickListener onClickListener) {
if (addGroupEnable) if (addEntryEnable)
addEntry.setOnClickListener(onClickListener); addEntryView.setOnClickListener(view -> {
onClickListener.onClick(view);
closeButtonIfOpen();
});
} }
private void startGlobalAnimation() { private void startGlobalAnimation() {
viewButtonMenuAnimation.startAnimation(); if (allowAction) {
viewButtonMenuAnimation.startAnimation();
if (addEntryEnable) { if (addEntryEnable) {
viewMenuAnimationAddEntry.startAnimation(); viewMenuAnimationAddEntry.startAnimation();
} }
if (addGroupEnable) { if (addGroupEnable) {
viewMenuAnimationAddGroup.startAnimation(); viewMenuAnimationAddGroup.startAnimation();
}
} }
} }
@@ -260,10 +276,6 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
this.delayOut = delayOut; this.delayOut = delayOut;
} }
ViewMenuAnimation(View view) {
this(view, 0, 0);
}
@Override @Override
public void onAnimationStart(View view) {} public void onAnimationStart(View view) {}
@@ -296,7 +308,7 @@ public class ListNodesWithAddButtonView extends RelativeLayout {
} else { } else {
// The first time // The first time
if (translation == 0) { 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.setTranslationY(-translation);
view.setTranslationX(view.getWidth()/3); view.setTranslationX(view.getWidth()/3);
view.setAlpha(0.0F); view.setAlpha(0.0F);

View File

@@ -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);
}

View File

@@ -84,25 +84,25 @@ public class EntryContentsView extends LinearLayout {
inflater.inflate(R.layout.entry_view_contents, this); inflater.inflate(R.layout.entry_view_contents, this);
userNameContainerView = findViewById(R.id.entry_user_name_container); userNameContainerView = findViewById(R.id.entry_user_name_container);
userNameView = (TextView) findViewById(R.id.entry_user_name); userNameView = findViewById(R.id.entry_user_name);
userNameActionView = (ImageView) findViewById(R.id.entry_user_name_action_image); userNameActionView = findViewById(R.id.entry_user_name_action_image);
passwordContainerView = findViewById(R.id.entry_password_container); passwordContainerView = findViewById(R.id.entry_password_container);
passwordView = (TextView) findViewById(R.id.entry_password); passwordView = findViewById(R.id.entry_password);
passwordActionView = (ImageView) findViewById(R.id.entry_password_action_image); passwordActionView = findViewById(R.id.entry_password_action_image);
urlContainerView = findViewById(R.id.entry_url_container); 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); 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); creationDateView = findViewById(R.id.entry_created);
modificationDateView = (TextView) findViewById(R.id.entry_modified); modificationDateView = findViewById(R.id.entry_modified);
lastAccessDateView = (TextView) findViewById(R.id.entry_accessed); lastAccessDateView = findViewById(R.id.entry_accessed);
expiresDateView = (TextView) findViewById(R.id.entry_expires); expiresDateView = findViewById(R.id.entry_expires);
} }
public void applyFontVisibilityToFields(boolean fontInVisibility) { public void applyFontVisibilityToFields(boolean fontInVisibility) {
@@ -113,6 +113,10 @@ public class EntryContentsView extends LinearLayout {
if (userName != null && !userName.isEmpty()) { if (userName != null && !userName.isEmpty()) {
userNameContainerView.setVisibility(VISIBLE); userNameContainerView.setVisibility(VISIBLE);
userNameView.setText(userName); 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()) { if (password != null && !password.isEmpty()) {
passwordContainerView.setVisibility(VISIBLE); passwordContainerView.setVisibility(VISIBLE);
passwordView.setText(password); passwordView.setText(password);
if (fontInVisibility)
Util.applyFontVisibilityTo(passwordView);
passwordActionView.setVisibility(GONE);
} else {
passwordContainerView.setVisibility(GONE);
} }
} }
public void assignPasswordCopyListener(OnClickListener onClickListener) { public void assignPasswordCopyListener(OnClickListener onClickListener) {
passwordActionView.setOnClickListener(onClickListener); passwordActionView.setOnClickListener(onClickListener);
passwordActionView.setVisibility(VISIBLE);
} }
public boolean isPasswordPresent() { public boolean isPasswordPresent() {
@@ -147,21 +157,25 @@ public class EntryContentsView extends LinearLayout {
if (url != null && !url.isEmpty()) { if (url != null && !url.isEmpty()) {
urlContainerView.setVisibility(VISIBLE); urlContainerView.setVisibility(VISIBLE);
urlView.setText(url); urlView.setText(url);
} else {
urlContainerView.setVisibility(GONE);
} }
} }
public void assignComment(String comment) { public void assignComment(String comment) {
if (comment != null && !comment.isEmpty()) { if (comment != null && !comment.isEmpty()) {
commentContainerView.setVisibility(VISIBLE); commentContainerView.setVisibility(VISIBLE);
Util.applyFontVisibilityToTextView(fontInVisibility, commentView);
commentView.setText(comment); commentView.setText(comment);
if (fontInVisibility)
Util.applyFontVisibilityTo(commentView);
} else {
commentContainerView.setVisibility(GONE);
} }
} }
public void addExtraField(String title, String value, OnClickListener onActionClickListener) { public void addExtraField(String title, String value, OnClickListener onActionClickListener) {
EntryNewField entryNewField = new EntryNewField(getContext(), null, title, value, onActionClickListener); EntryNewField entryNewField = new EntryNewField(getContext(), null, title, value, onActionClickListener);
entryNewField.applyFontVisibilityToValue(fontInVisibility); entryNewField.applyFontVisibility(fontInVisibility);
extrasView.addView(entryNewField); extrasView.addView(entryNewField);
} }

View File

@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
@@ -36,7 +37,7 @@ import com.kunzisoft.keepass.R;
public class EntryEditNewField extends RelativeLayout { public class EntryEditNewField extends RelativeLayout {
private TextView labelView; private TextView labelView;
private TextView valueView; private EditText valueView;
private CompoundButton protectionCheckView; private CompoundButton protectionCheckView;
public EntryEditNewField(Context context) { public EntryEditNewField(Context context) {
@@ -55,16 +56,11 @@ public class EntryEditNewField extends RelativeLayout {
inflater.inflate(R.layout.entry_edit_new_field, this); inflater.inflate(R.layout.entry_edit_new_field, this);
View deleteView = findViewById(R.id.entry_edit_new_field_delete); View deleteView = findViewById(R.id.entry_edit_new_field_delete);
deleteView.setOnClickListener(new OnClickListener() { deleteView.setOnClickListener(v -> deleteViewFromParent());
@Override
public void onClick(View v) {
deleteViewFromParent();
}
});
labelView = (TextView) findViewById(R.id.entry_edit_new_field_label); labelView = findViewById(R.id.entry_edit_new_field_label);
valueView = (TextView) findViewById(R.id.entry_edit_new_field_value); valueView = findViewById(R.id.entry_edit_new_field_value);
protectionCheckView = (CompoundButton) findViewById(R.id.protection); protectionCheckView = findViewById(R.id.protection);
} }
public void setData(String label, ProtectedString value) { public void setData(String label, ProtectedString value) {
@@ -89,7 +85,8 @@ public class EntryEditNewField extends RelativeLayout {
} }
public void setFontVisibility(boolean applyFontVisibility) { public void setFontVisibility(boolean applyFontVisibility) {
Util.applyFontVisibilityToTextView(applyFontVisibility, valueView); if (applyFontVisibility)
Util.applyFontVisibilityTo(valueView);
} }
public void deleteViewFromParent() { public void deleteViewFromParent() {

View File

@@ -54,17 +54,18 @@ public class EntryNewField extends LinearLayout {
assert inflater != null; assert inflater != null;
inflater.inflate(R.layout.entry_new_field, this); inflater.inflate(R.layout.entry_new_field, this);
labelView = (TextView) findViewById(R.id.title); labelView = findViewById(R.id.title);
valueView = (TextView) findViewById(R.id.value); valueView = findViewById(R.id.value);
actionImageView = (ImageView) findViewById(R.id.action_image); actionImageView = findViewById(R.id.action_image);
setLabel(label); setLabel(label);
setValue(value); setValue(value);
setAction(onClickActionListener); setAction(onClickActionListener);
} }
public void applyFontVisibilityToValue(boolean changeFont) { public void applyFontVisibility(boolean fontInVisibility) {
Util.applyFontVisibilityToTextView(changeFont, valueView); if (fontInVisibility)
Util.applyFontVisibilityTo(valueView);
} }
public void setLabel(String label) { public void setLabel(String label) {

View File

@@ -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>();
}
}

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

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