Compare commits

..

1 Commits

Author SHA1 Message Date
J-Jamet
df33c5feb5 First commit for cancelable loading 2019-10-24 14:53:55 +02:00
267 changed files with 7873 additions and 10694 deletions

View File

@@ -38,7 +38,7 @@ If applicable, add screenshots to help explain your problem.
**Android (please complete the following information):**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
- Browser: [e.g. Chrome]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

7
.gitignore vendored
View File

@@ -38,13 +38,6 @@ proguard/
# Android Studio captures folder
captures/
# Eclipse/VS Code
.project
.settings/*
*/.project
*/.classpath
*/.settings/*
# Intellij
*.iml
.idea/workspace.xml

View File

@@ -1,20 +1,12 @@
KeepassDX (2.5.0.0beta25)
* Setting for Recycle Bin
* Fix Recycle bin issues
* Fix TOTP
* Fix infinite save
* Fix update group
* Fix OOM
KeepassDX (2.5.0.0beta24)
* Add OTP (HOTP / TOTP)
* Add settings (Color, Security, Master Key)
* Show history of each entry
* Auto repair database for nodes with same UUID
* Management of expired nodes
* Multi-selection for actions (Cut - Copy - Delete)
* Open/Save database as service / Add persistent notification
* Fix settings / edit group / small bugs
* Fix settings
* Fix edit group
* Fix small bugs
KeepassDX (2.5.0.0beta23)
* New, more secure database creation workflow

View File

@@ -10,8 +10,7 @@
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePass XC...)
* Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)*
* **Fingerprint** for fast unlocking
* Material design with **themes**
* **AutoFill** and Integration
* Field filling **keyboard**
@@ -55,7 +54,7 @@ You can contribute in different ways to help us on our work.
## F.A.Q.
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/wiki/F.A.Q.)
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
## Other devices

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

@@ -6,13 +6,14 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
ndkVersion "20.0.5594570"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 28
versionCode = 25
versionName = "2.5.0.0beta25"
versionCode = 24
versionName = "2.5.0.0beta24"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -27,6 +28,7 @@ android {
}
}
buildTypes {
release {
minifyEnabled = false
@@ -79,7 +81,7 @@ android {
}
def spongycastleVersion = "1.58.0.0"
def room_version = "2.2.1"
def room_version = "2.2.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -88,7 +90,7 @@ dependencies {
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.biometric:biometric:1.0.0'
implementation 'androidx.biometric:biometric:1.0.0-rc01'
implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.room:room-runtime:$room_version"
@@ -105,8 +107,8 @@ dependencies {
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
// Apache Commons Codec
implementation 'commons-codec:commons-codec:1.11'
// Base64
implementation 'biz.source_code:base64coder:2010-12-19'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

View File

@@ -21,18 +21,17 @@ package com.kunzisoft.keepass.tests
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.element.PwDate
import org.junit.Assert
class PwDateTest : TestCase() {
fun testDate() {
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = DatabaseInputOutputUtils.readCDate(DatabaseInputOutputUtils.writeCDate(intermediate.date)!!, 0)
val jDate = PwDate(System.currentTimeMillis())
val intermediate = PwDate(jDate)
val cDate = PwDate(intermediate.byteArrayDate!!, 0)
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
Assert.assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
Assert.assertTrue("jDate and cDate not equal", cDate == jDate)
}
}

View File

@@ -27,11 +27,12 @@ import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.PwDate
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.utils.Types
class DatabaseInputOutputUtilsTest : TestCase() {
class TypesTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -154,8 +155,8 @@ class DatabaseInputOutputUtilsTest : TestCase() {
setArray(orig, value, 0, 1)
val one = DatabaseInputOutputUtils.readUByte(orig, 0)
DatabaseInputOutputUtils.writeUByte(one, dest, 0)
val one = Types.readUByte(orig, 0)
Types.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
@@ -167,31 +168,27 @@ class DatabaseInputOutputUtilsTest : TestCase() {
val expected = Calendar.getInstance()
expected.set(2008, 1, 2, 3, 4, 5)
val buf = PwDate.writeTime(expected.time, cal)
val actual = Calendar.getInstance()
DatabaseInputOutputUtils.writeCDate(expected.time, cal)?.let { buf ->
actual.time = DatabaseInputOutputUtils.readCDate(buf, 0, cal).date
}
actual.time = PwDate.readTime(buf, 0, cal)
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
}
fun testUUID() {
val rnd = Random()
val bUUID = ByteArray(16)
Random().nextBytes(bUUID)
rnd.nextBytes(bUUID)
val uuid = DatabaseInputOutputUtils.bytesToUuid(bUUID)
val eUUID = DatabaseInputOutputUtils.uuidToBytes(uuid)
val lUUID = LEDataInputStream.readUuid(bUUID, 0)
val leUUID = DatabaseInputOutputUtils.uuidToBytes(lUUID)
val uuid = Types.bytestoUUID(bUUID)
val eUUID = Types.UUIDtoBytes(uuid)
assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID)
}
@Throws(Exception::class)
@@ -203,7 +200,7 @@ class DatabaseInputOutputUtilsTest : TestCase() {
val bos = ByteArrayOutputStream()
val leos = LEDataOutputStream(bos)
leos.writeLong(DatabaseInputOutputUtils.ULONG_MAX_VALUE)
leos.writeLong(Types.ULONG_MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -20,7 +20,6 @@
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:theme="@style/KeepassDXStyle.Night">
<!-- TODO backup API Key -->
<meta-data
@@ -142,10 +141,7 @@
</intent-filter>
</activity>
<service
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true"

View File

@@ -24,22 +24,21 @@ import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import com.google.android.material.appbar.CollapsingToolbarLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -59,12 +58,11 @@ class EntryActivity : LockingHideActivity() {
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var entryContentsView: EntryContentsView? = null
private var entryProgress: ProgressBar? = null
private var toolbar: Toolbar? = null
private var mDatabase: Database? = null
private var mEntry: Entry? = null
private var mEntry: EntryVersioned? = null
private var mIsHistory: Boolean = false
private var mShowPassword: Boolean = false
@@ -103,7 +101,6 @@ class EntryActivity : LockingHideActivity() {
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryProgress = findViewById(R.id.entry_progress)
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
@@ -115,7 +112,7 @@ class EntryActivity : LockingHideActivity() {
// Get Entry from UUID
try {
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
val keyEntry: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry)
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
@@ -158,7 +155,7 @@ class EntryActivity : LockingHideActivity() {
firstLaunchOfActivity = false
}
private fun fillEntryDataInContentsView(entry: Entry) {
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
val database = Database.getInstance()
database.startManageEntry(entry)
@@ -223,17 +220,6 @@ class EntryActivity : LockingHideActivity() {
}
}
//Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
View.OnClickListener {
entry.getOtpElement()?.let { otpElement ->
clipboardHelper?.timeoutCopyToClipboard(
otpElement.token,
getString(R.string.copy_field, getString(R.string.entry_otp))
)
}
})
entryContentsView?.assignURL(entry.url)
entryContentsView?.assignComment(entry.notes)
@@ -298,8 +284,6 @@ class EntryActivity : LockingHideActivity() {
}
database.stopManageEntry(entry)
entryContentsView?.refreshHistory()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -329,9 +313,9 @@ class EntryActivity : LockingHideActivity() {
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
inflater.inflate(R.menu.database_lock, menu)
if (mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false
}
@@ -400,18 +384,21 @@ class EntryActivity : LockingHideActivity() {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true
}
R.id.menu_edit -> {
mEntry?.let {
EntryEditActivity.launch(this@EntryActivity, it)
}
return true
}
R.id.menu_goto_url -> {
var url: String = mEntry?.url ?: ""
@@ -421,17 +408,18 @@ class EntryActivity : LockingHideActivity() {
}
UriUtil.gotoUrl(this, url)
return true
}
R.id.menu_lock -> {
lockAndExit()
return true
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
}
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item)
}
@@ -442,7 +430,7 @@ class EntryActivity : LockingHideActivity() {
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
onFinish(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish()
}
@@ -453,7 +441,7 @@ class EntryActivity : LockingHideActivity() {
const val KEY_ENTRY = "KEY_ENTRY"
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)

View File

@@ -29,20 +29,14 @@ import android.view.View
import android.widget.ScrollView
import androidx.appcompat.widget.Toolbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
@@ -51,16 +45,15 @@ import java.util.*
class EntryEditActivity : LockingHideActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener {
GeneratePasswordDialogFragment.GeneratePasswordListener {
private var mDatabase: Database? = null
// Refs of an entry and group in database, are not modifiable
private var mEntry: Entry? = null
private var mParent: Group? = null
private var mEntry: EntryVersioned? = null
private var mParent: GroupVersioned? = null
// New or copy of mEntry in the database to be modifiable
private var mNewEntry: Entry? = null
private var mNewEntry: EntryVersioned? = null
private var mIsNew: Boolean = false
// Views
@@ -68,6 +61,9 @@ class EntryEditActivity : LockingHideActivity(),
private var entryEditContentsView: EntryEditContentsView? = null
private var saveView: View? = null
// Dialog thread
private var progressDialogThread: ProgressDialogThread? = null
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -89,14 +85,11 @@ class EntryEditActivity : LockingHideActivity(),
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
// Likely the app has been killed exit the activity
mDatabase = Database.getInstance()
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
intent.getParcelableExtra<PwNodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false
// Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it)
@@ -116,7 +109,7 @@ class EntryEditActivity : LockingHideActivity(),
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mEntry?.let { entry ->
// Create a copy to modify
mNewEntry = Entry(entry).also { newEntry ->
mNewEntry = EntryVersioned(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
@@ -125,7 +118,7 @@ class EntryEditActivity : LockingHideActivity(),
}
// Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
mIsNew = true
// Create an empty new entry
if (savedInstanceState == null
@@ -166,15 +159,13 @@ class EntryEditActivity : LockingHideActivity(),
saveView = findViewById(R.id.entry_edit_save)
saveView?.setOnClickListener { saveEntry() }
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) {
addNewCustomField()
}
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) { addNewCustomField() }
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
// Create progress dialog
mProgressDialogThread?.onActionFinish = { actionTask, result ->
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
@@ -185,7 +176,7 @@ class EntryEditActivity : LockingHideActivity(),
}
}
private fun populateViewsWithEntry(newEntry: Entry) {
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
// Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry)
@@ -201,13 +192,13 @@ class EntryEditActivity : LockingHideActivity(),
notes = newEntry.notes
for (entry in newEntry.customFields.entries) {
post {
putCustomField(entry.key, entry.value)
addNewCustomField(entry.key, entry.value)
}
}
}
}
private fun populateEntryWithViews(newEntry: Entry) {
private fun populateEntryWithViews(newEntry: EntryVersioned) {
mDatabase?.startManageEntry(newEntry)
@@ -221,7 +212,7 @@ class EntryEditActivity : LockingHideActivity(),
password = entryView.password
notes = entryView.notes
entryView.customFields.forEach { customField ->
putExtraField(customField.name, customField.protectedValue)
addExtraField(customField.name, customField.protectedValue)
}
}
}
@@ -229,7 +220,7 @@ class EntryEditActivity : LockingHideActivity(),
mDatabase?.stopManageEntry(newEntry)
}
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon)
@@ -247,7 +238,7 @@ class EntryEditActivity : LockingHideActivity(),
* Add a new customized field view and scroll to bottom
*/
private fun addNewCustomField() {
entryEditContentsView?.addEmptyCustomField()
entryEditContentsView?.addNewCustomField()
}
/**
@@ -263,26 +254,26 @@ class EntryEditActivity : LockingHideActivity(),
// WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent
// Build info
newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant()
newEntry.lastAccessTime = PwDate()
newEntry.lastModificationTime = PwDate()
populateEntryWithViews(newEntry)
// Open a progress dialog and save entry
if (mIsNew) {
mParent?.let { parent ->
mProgressDialogThread?.startDatabaseCreateEntry(
progressDialogThread?.startDatabaseCreateEntry(
newEntry,
parent,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
}
} else {
mEntry?.let { oldEntry ->
mProgressDialogThread?.startDatabaseUpdateEntry(
progressDialogThread?.startDatabaseUpdateEntry(
oldEntry,
newEntry,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
}
}
@@ -290,16 +281,24 @@ class EntryEditActivity : LockingHideActivity(),
}
}
override fun onResume() {
super.onResume()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.database, menu)
// Save database not needed here
menu.findItem(R.id.menu_save_database)?.isVisible = false
inflater.inflate(R.menu.database_lock, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
if (mDatabase?.allowOTP == true)
inflater.inflate(R.menu.entry_otp, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
@@ -310,7 +309,7 @@ class EntryEditActivity : LockingHideActivity(),
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordView = entryEditContentsView?.generatePasswordView
val addNewFieldView = entryEditContentsView?.addNewFieldButton
val addNewFieldView = entryEditContentsView?.addNewFieldView
val generatePasswordEducationPerformed = passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
@@ -340,34 +339,18 @@ class EntryEditActivity : LockingHideActivity(),
lockAndExit()
return true
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
}
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_add_otp -> {
// Retrieve the current otpElement if exists
// and open the dialog to set up the OTP
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
.show(supportFragmentManager, "addOTPDialog")
return true
}
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
override fun onOtpCreated(otpElement: OtpElement) {
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
entryEditContentsView?.putCustomField(otpField.name, otpField.protectedValue)
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
}
override fun iconPicked(bundle: Bundle) {
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
temporarilySaveAndShowSelectedIcon(icon)
@@ -441,7 +424,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity
* @param pwEntry Entry to update
*/
fun launch(activity: Activity, pwEntry: Entry) {
fun launch(activity: Activity, pwEntry: EntryVersioned) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
@@ -455,7 +438,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity
* @param pwGroup Group who will contains new entry
*/
fun launch(activity: Activity, pwGroup: Group) {
fun launch(activity: Activity, pwGroup: GroupVersioned) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, pwGroup.nodeId)

View File

@@ -76,7 +76,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mOpenFileHelper: OpenFileHelper? = null
private var mProgressDialogThread: ProgressDialogThread? = null
private var progressDialogThread: ProgressDialogThread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -163,15 +163,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Attach the dialog thread to this activity
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, _ ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
// TODO Check
// mAdapterDatabaseHistory?.notifyDataSetChanged()
// updateFileListVisibility()
GroupActivity.launch(this@FileDatabaseSelectActivity)
}
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
// TODO Check
// mAdapterDatabaseHistory?.notifyDataSetChanged()
// updateFileListVisibility()
GroupActivity.launch(this)
}
}
}
@@ -298,12 +296,12 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Register progress task
mProgressDialogThread?.registerProgressTask()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
// Unregister progress task
mProgressDialogThread?.unregisterProgressTask()
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -331,7 +329,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
mProgressDialogThread?.startDatabaseCreate(
progressDialogThread?.startDatabaseCreate(
databaseUri,
masterPasswordChecked,
masterPassword,

View File

@@ -42,18 +42,18 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -77,7 +77,6 @@ class GroupActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener,
ListNodesFragment.NodeClickListener,
ListNodesFragment.NodesActionMenuListener,
DeleteNodesDialogFragment.DeleteNodeListener,
ListNodesFragment.OnScrollListener,
SortDialogFragment.SortSelectionListener {
@@ -97,10 +96,14 @@ class GroupActivity : LockingActivity(),
private var mListNodesFragment: ListNodesFragment? = null
private var mCurrentGroupIsASearch: Boolean = false
private var progressDialogThread: ProgressDialogThread? = null
// Nodes
private var mRootGroup: Group? = null
private var mCurrentGroup: Group? = null
private var mOldGroupToUpdate: Group? = null
private var mRootGroup: GroupVersioned? = null
private var mCurrentGroup: GroupVersioned? = null
private var mOldGroupToUpdate: GroupVersioned? = null
// TODO private var mNodeToCopy: NodeVersioned? = null
// TODO private var mNodeToMove: NodeVersioned? = null
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
@@ -131,6 +134,14 @@ class GroupActivity : LockingActivity(),
toolbar?.title = ""
setSupportActionBar(toolbar)
/*
toolbarAction?.setNavigationOnClickListener {
toolbarAction?.collapse()
mNodeToCopy = null
mNodeToMove = null
}
*/
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
@@ -196,13 +207,13 @@ class GroupActivity : LockingActivity(),
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
// Init dialog thread
mProgressDialogThread?.onActionFinish = { actionTask, result ->
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
var oldNodes: List<Node> = ArrayList()
var oldNodes: List<NodeVersioned> = ArrayList()
result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle ->
oldNodes = getListNodesFromBundle(database, oldNodesBundle)
}
var newNodes: List<Node> = ArrayList()
var newNodes: List<NodeVersioned> = ArrayList()
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
newNodes = getListNodesFromBundle(database, newNodesBundle)
}
@@ -223,23 +234,24 @@ class GroupActivity : LockingActivity(),
ACTION_DATABASE_DELETE_NODES_TASK -> {
if (result.isSuccess) {
// Rebuild all the list to avoid bug when delete node from sort
mListNodesFragment?.rebuildList()
// Rebuild all the list the avoid bug when delete node from db sort
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) {
mListNodesFragment?.rebuildList()
} else {
// Use the old Nodes / entries unchanged with the old parent
mListNodesFragment?.removeNodes(oldNodes)
}
// Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) {
val recycleBin = database.recycleBin
val currentGroup = mCurrentGroup
if (currentGroup != null && recycleBin != null
&& currentGroup != recycleBin) {
// Recycle bin already here, simply update it
if (mListNodesFragment?.contains(recycleBin) == true) {
if (mCurrentGroup != null && recycleBin != null
&& mCurrentGroup!!.parent == null
&& mCurrentGroup != recycleBin) {
if (mListNodesFragment?.contains(recycleBin) == true)
mListNodesFragment?.updateNode(recycleBin)
}
// Recycle bin not here, verify if parents are similar to add it
else if (currentGroup == recycleBin.parent) {
else
mListNodesFragment?.addNode(recycleBin)
}
}
}
}
@@ -247,18 +259,14 @@ class GroupActivity : LockingActivity(),
}
if (!result.isSuccess) {
coordinatorLayout?.let { coordinatorLayout ->
result.exception?.errorId?.let { errorId ->
result.exception?.errorId?.let { errorId ->
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
} ?: result.message?.let { message ->
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show()
}
}
}
finishNodeAction()
refreshNumberOfChildren()
}
}
@@ -281,7 +289,7 @@ class GroupActivity : LockingActivity(),
}
}
private fun openSearchGroup(group: Group?) {
private fun openSearchGroup(group: GroupVersioned?) {
// Delete the previous search fragment
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) {
@@ -293,11 +301,11 @@ class GroupActivity : LockingActivity(),
openGroup(group, true)
}
private fun openChildGroup(group: Group) {
private fun openChildGroup(group: GroupVersioned) {
openGroup(group, false)
}
private fun openGroup(group: Group?, isASearch: Boolean) {
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) {
// Check TimeoutHelper
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
// Open a group in a new fragment
@@ -337,7 +345,7 @@ class GroupActivity : LockingActivity(),
super.onSaveInstanceState(outState)
}
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? {
// Force read only if the database is like that
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
@@ -348,7 +356,7 @@ class GroupActivity : LockingActivity(),
}
// else a real group
else {
var pwGroupId: NodeId<*>? = null
var pwGroupId: PwNodeId<*>? = null
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
} else {
@@ -357,7 +365,7 @@ class GroupActivity : LockingActivity(),
}
Log.w(TAG, "Creating tree view")
val currentGroup: Group?
val currentGroup: GroupVersioned?
currentGroup = if (pwGroupId == null) {
mRootGroup
} else {
@@ -414,7 +422,14 @@ class GroupActivity : LockingActivity(),
}
// Assign number of children
refreshNumberOfChildren()
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
// Show selection mode message if needed
if (mSelectionMode) {
@@ -445,31 +460,20 @@ class GroupActivity : LockingActivity(),
}
}
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
}
override fun onScrolled(dy: Int) {
addNodeButtonView?.hideButtonOnScrollListener(dy)
}
override fun onNodeClick(node: Node) {
override fun onNodeClick(node: NodeVersioned) {
when (node.type) {
Type.GROUP -> try {
openChildGroup(node as Group)
openChildGroup(node as GroupVersioned)
} catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group")
}
Type.ENTRY -> try {
val entryVersioned = node as Entry
val entryVersioned = node as EntryVersioned
EntrySelectionHelper.doEntrySelectionAction(intent,
{
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
@@ -487,10 +491,8 @@ class GroupActivity : LockingActivity(),
{
// Build response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
mDatabase?.let { database ->
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
entryVersioned.getEntryInfo(database))
}
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
entryVersioned.getEntryInfo(mDatabase!!))
}
finish()
})
@@ -507,7 +509,7 @@ class GroupActivity : LockingActivity(),
actionNodeMode = null
}
override fun onNodeSelected(nodes: List<Node>): Boolean {
override fun onNodeSelected(nodes: List<NodeVersioned>): Boolean {
if (nodes.isNotEmpty()) {
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
@@ -522,34 +524,34 @@ class GroupActivity : LockingActivity(),
return true
}
override fun onOpenMenuClick(node: Node): Boolean {
override fun onOpenMenuClick(node: NodeVersioned): Boolean {
finishNodeAction()
onNodeClick(node)
return true
}
override fun onEditMenuClick(node: Node): Boolean {
override fun onEditMenuClick(node: NodeVersioned): Boolean {
finishNodeAction()
when (node.type) {
Type.GROUP -> {
mOldGroupToUpdate = node as Group
mOldGroupToUpdate = node as GroupVersioned
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
.show(supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP)
}
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned)
}
return true
}
override fun onCopyMenuClick(nodes: List<Node>): Boolean {
override fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
return true
}
override fun onMoveMenuClick(nodes: List<Node>): Boolean {
override fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
@@ -557,77 +559,41 @@ class GroupActivity : LockingActivity(),
}
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<Node>): Boolean {
// Move or copy only if allowed (in root if allowed)
if (mCurrentGroup != mDatabase?.rootGroup
|| mDatabase?.rootCanContainsEntry() == true) {
when (pasteMode) {
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
mCurrentGroup?.let { newParent ->
mProgressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
mProgressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {
nodes: List<NodeVersioned>): Boolean {
when (pasteMode) {
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly
)
}
}
} else {
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout,
R.string.error_copy_entry_here,
Snackbar.LENGTH_LONG).asError().show()
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly
)
}
}
else -> {}
}
finishNodeAction()
return true
}
override fun onDeleteMenuClick(nodes: List<Node>): Boolean {
val database = mDatabase
// If recycle bin enabled, ensure it exists
if (database != null && database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (database != null
&& database.isRecycleBinEnabled
&& database.recycleBin != mCurrentGroup) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
DeleteNodesDialogFragment.getInstance(nodes)
.show(supportFragmentManager, "deleteNodesDialogFragment")
}
finishNodeAction()
return true
}
override fun permanentlyDeleteNodes(nodes: List<Node>) {
mProgressDialogThread?.startDatabaseDeleteNodes(
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean {
progressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
finishNodeAction()
return true
}
override fun onResume() {
@@ -636,9 +602,13 @@ class GroupActivity : LockingActivity(),
assignGroupViewElements()
// Refresh suggestions to change preferences
mSearchSuggestionAdapter?.reInit(this)
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
finishNodeAction()
@@ -648,21 +618,12 @@ class GroupActivity : LockingActivity(),
val inflater = menuInflater
inflater.inflate(R.menu.search, menu)
inflater.inflate(R.menu.database, menu)
if (mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
}
inflater.inflate(R.menu.database_lock, menu)
if (!mSelectionMode) {
inflater.inflate(R.menu.default_menu, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
}
// Menu for recycle bin
if (mDatabase?.isRecycleBinEnabled == true
&& mDatabase?.recycleBin == mCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu)
}
// Get the SearchView and set the searchable configuration
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
@@ -771,17 +732,6 @@ class GroupActivity : LockingActivity(),
lockAndExit()
return true
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true
}
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
onDeleteMenuClick(listChildren)
}
return true
}
else -> {
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, mReadOnly, true)
@@ -792,7 +742,7 @@ class GroupActivity : LockingActivity(),
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: IconImage?) {
icon: PwIcon?) {
if (name != null && name.isNotEmpty() && icon != null) {
when (action) {
@@ -806,33 +756,28 @@ class GroupActivity : LockingActivity(),
// Not really needed here because added in runnable but safe
newGroup.parent = currentGroup
mProgressDialogThread?.startDatabaseCreateGroup(
newGroup,
currentGroup,
!mReadOnly && mAutoSaveEnable
)
progressDialogThread?.startDatabaseCreateGroup(
newGroup, currentGroup, !mReadOnly)
}
}
}
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
// If update add new elements
mOldGroupToUpdate?.let { oldGroupToUpdate ->
val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
GroupVersioned(oldGroupToUpdate).let { updateGroup ->
updateGroup.apply {
// WARNING remove parent and children to keep memory
removeParent()
removeChildren()
removeChildren() // TODO concurrent exception
title = name
this.icon = icon // TODO custom icon
}
// If group updated save it in the database
progressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate, updateGroup, !mReadOnly)
}
// If group updated save it in the database
mProgressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate,
updateGroup,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {}
@@ -842,7 +787,7 @@ class GroupActivity : LockingActivity(),
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: IconImage?) {
icon: PwIcon?) {
// Do nothing here
}
@@ -953,7 +898,7 @@ class GroupActivity : LockingActivity(),
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
private fun buildAndLaunchIntent(context: Context, group: Group?, readOnly: Boolean,
private fun buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
val checkTime = if (context is Activity)
TimeoutHelper.checkTimeAndLockIfTimeout(context)

View File

@@ -18,16 +18,16 @@ import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.Type
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -36,7 +36,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private var onScrollListener: OnScrollListener? = null
private var listView: RecyclerView? = null
var mainGroup: Group? = null
var mainGroup: GroupVersioned? = null
private set
private var mAdapter: NodeAdapter? = null
@@ -44,8 +44,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
private set
private val listActionNodes = LinkedList<Node>()
private val listPasteNodes = LinkedList<Node>()
private val listActionNodes = LinkedList<NodeVersioned>()
private val listPasteNodes = LinkedList<NodeVersioned>()
private var notFoundView: View? = null
private var isASearchResult: Boolean = false
@@ -103,7 +103,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter = NodeAdapter(context)
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: Node) {
override fun onNodeClick(node: NodeVersioned) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
@@ -120,7 +120,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
override fun onNodeLongClick(node: Node): Boolean {
override fun onNodeLongClick(node: NodeVersioned): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
@@ -228,7 +228,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().isRecycleBinEnabled) {
if (Database.getInstance().allowRecycleBin
&& Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
@@ -250,7 +251,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
fun actionNodesCallback(nodes: List<Node>,
fun actionNodesCallback(nodes: List<NodeVersioned>,
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
return object : ActionMode.Callback {
@@ -275,8 +276,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
if (readOnly || nodes[0] == database.recycleBin) {
menu?.removeItem(R.id.menu_edit)
}
} else {
@@ -287,6 +287,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Copy and Move (not for groups)
if (readOnly
|| isASearchResult
|| nodes.any { it == database.recycleBin }
|| nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group
menu?.removeItem(R.id.menu_copy)
@@ -294,8 +295,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Deletion
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
if (readOnly || nodes.any { it == database.recycleBin }) {
menu?.removeItem(R.id.menu_delete)
}
}
@@ -354,7 +354,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
data?.getParcelableExtra<NodeVersioned>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter?.addNode(newNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
@@ -369,31 +369,31 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
fun contains(node: Node): Boolean {
fun contains(node: NodeVersioned): Boolean {
return mAdapter?.contains(node) ?: false
}
fun addNode(newNode: Node) {
fun addNode(newNode: NodeVersioned) {
mAdapter?.addNode(newNode)
}
fun addNodes(newNodes: List<Node>) {
fun addNodes(newNodes: List<NodeVersioned>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: Node, newNode: Node? = null) {
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
}
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
mAdapter?.updateNodes(oldNodes, newNodes)
}
fun removeNode(pwNode: Node) {
fun removeNode(pwNode: NodeVersioned) {
mAdapter?.removeNode(pwNode)
}
fun removeNodes(nodes: List<Node>) {
fun removeNodes(nodes: List<NodeVersioned>) {
mAdapter?.removeNodes(nodes)
}
@@ -409,20 +409,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean
fun onNodeClick(node: NodeVersioned)
fun onNodeSelected(nodes: List<NodeVersioned>): Boolean
}
/**
* Menu listener to redefine to do an action in menu
*/
interface NodesActionMenuListener {
fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
fun onOpenMenuClick(node: NodeVersioned): Boolean
fun onEditMenuClick(node: NodeVersioned): Boolean
fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean
}
enum class PasteMode {
@@ -447,7 +447,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)

View File

@@ -47,6 +47,7 @@ import androidx.biometric.BiometricManager
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -58,7 +59,7 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
@@ -101,7 +102,7 @@ class PasswordActivity : StylishActivity() {
private var readOnly: Boolean = false
private var mProgressDialogThread: ProgressDialogThread? = null
private var progressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
@@ -128,7 +129,7 @@ class PasswordActivity : StylishActivity() {
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
advancedUnlockInfoView = findViewById(R.id.fingerprint_info)
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
@@ -163,18 +164,32 @@ class PasswordActivity : StylishActivity() {
enableOrNotTheConfirmationButton()
}
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck biometric if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
}
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
// Remove the password in view in all cases
removePassword()
@@ -187,43 +202,24 @@ class PasswordActivity : StylishActivity() {
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
// Relaunch loading if we need to fix UUID
if (resultException is DuplicateUuidDatabaseException) {
if (resultException is LoadDatabaseDuplicateUuidException)
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError, resultException)
Snackbar.make(activity_password_coordinator_layout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
@@ -274,7 +270,7 @@ class PasswordActivity : StylishActivity() {
// For check shutdown
super.onResume()
mProgressDialogThread?.registerProgressTask()
progressDialogThread?.registerProgressTask()
initUriFromIntent()
}
@@ -370,11 +366,15 @@ class PasswordActivity : StylishActivity() {
if (launchImmediately) {
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else {
// Init Biometric elements
var biometricInitialize = false
// Init FingerPrint elements
var fingerPrintInit = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
advancedUnlockInfoView?.setOnClickListener {
FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog")
}
if (advancedUnlockedManager == null && databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri,
@@ -396,18 +396,18 @@ class PasswordActivity : StylishActivity() {
{ passwordDecrypted ->
// Load the database if password is retrieve from biometric
passwordDecrypted?.let {
// Retrieve from biometric
// Retrieve from fingerprint
verifyKeyFileCheckboxAndLoadDatabase(it)
}
})
}
advancedUnlockedManager?.checkBiometricAvailability()
biometricInitialize = true
advancedUnlockedManager?.initBiometric()
fingerPrintInit = true
} else {
advancedUnlockedManager?.destroy()
}
}
if (!biometricInitialize) {
if (!fingerPrintInit) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
@@ -461,7 +461,11 @@ class PasswordActivity : StylishActivity() {
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.pause()
}
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -529,7 +533,7 @@ class PasswordActivity : StylishActivity() {
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDialogThread?.startDatabaseLoad(
progressDialogThread?.startDatabaseLoad(
databaseUri,
password,
keyFile,
@@ -557,7 +561,7 @@ class PasswordActivity : StylishActivity() {
MenuUtil.defaultMenuInflater(inflater, menu)
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
// Fingerprint menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
}
@@ -601,12 +605,12 @@ class PasswordActivity : StylishActivity() {
if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
// EducationPerformed
// fingerprintEducationPerformed
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!)
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!)
}
}
@@ -630,7 +634,7 @@ class PasswordActivity : StylishActivity() {
readOnly = !readOnly
changeOpenFileReadIcon(item)
}
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.deleteEntryKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
class DeleteNodesDialogFragment : DialogFragment() {
private var mNodesToDelete: List<Node> = ArrayList()
private var mListener: DeleteNodeListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DeleteNodeListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DeleteNodeListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
arguments?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this)
}
} ?: savedInstanceState?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState)
}
}
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(getString(R.string.warning_permanently_delete_nodes))
builder.setPositiveButton(android.R.string.yes) { _, _ ->
mListener?.permanentlyDeleteNodes(mNodesToDelete)
}
builder.setNegativeButton(android.R.string.no) { _, _ -> dismiss() }
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putAll(getBundleFromListNodes(mNodesToDelete))
}
interface DeleteNodeListener {
fun permanentlyDeleteNodes(nodes: List<Node>)
}
companion object {
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
return DeleteNodesDialogFragment().apply {
arguments = getBundleFromListNodes(nodesToDelete)
}
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintExplanationDialog : DialogFragment() {
private var fingerPrintAnimatedVector: FingerPrintAnimatedVector? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_fingerprint_explanation, null)
rootView.findViewById<View>(R.id.fingerprint_setting_link_text).setOnClickListener {
startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS))
}
rootView.findViewById<View>(R.id.auto_open_biometric_prompt_button).setOnClickListener {
startActivity(Intent(activity, SettingsAdvancedUnlockActivity::class.java))
}
fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity,
rootView.findViewById(R.id.biometric_image))
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
fingerPrintAnimatedVector?.startScan()
}
override fun onPause() {
super.onPause()
fingerPrintAnimatedVector?.stopScan()
}
}

View File

@@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
private var editGroupDialogAction: EditGroupDialogAction? = null
private var nameGroup: String? = null
private var iconGroup: IconImage? = null
private var iconGroup: PwIcon? = null
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
}
interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
}
companion object {
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
return fragment
}
fun build(group: Group): GroupEditDialogFragment {
fun build(group: GroupVersioned): GroupEditDialogFragment {
val bundle = Bundle()
bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon)

View File

@@ -35,7 +35,7 @@ import android.widget.GridView
import android.widget.ImageView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.PwIconStandard
import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser
@@ -72,7 +72,7 @@ class IconPickerDialogFragment : DialogFragment() {
currIconGridView.setOnItemClickListener { _, _, position, _ ->
val bundle = Bundle()
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
iconPickerListener?.iconPicked(bundle)
dismiss()
}
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? {
return bundle.getParcelable(KEY_ICON_STANDARD)
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.provider.Settings
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class KeyboardExplanationDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let {
val builder = AlertDialog.Builder(activity!!)
val inflater = activity!!.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null)
rootView.findViewById<View>(R.id.keyboards_activate_device_setting_button)
.setOnClickListener { launchActivateKeyboardSetting() }
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
if (BuildConfig.CLOSED_STORE) {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
} else {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
}
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun launchActivateKeyboardSetting() {
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}

View File

@@ -1,382 +0,0 @@
package com.kunzisoft.keepass.activities.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.OtpModel
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpTokenType
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.otp.TokenCalculator
import java.util.*
class SetOTPDialogFragment : DialogFragment() {
private var mCreateOTPElementListener: CreateOtpListener? = null
private var mOtpElement: OtpElement = OtpElement()
private var otpTypeSpinner: Spinner? = null
private var otpTokenTypeSpinner: Spinner? = null
private var otpSecretContainer: TextInputLayout? = null
private var otpSecretTextView: EditText? = null
private var otpPeriodContainer: TextInputLayout? = null
private var otpPeriodTextView: EditText? = null
private var otpCounterContainer: TextInputLayout? = null
private var otpCounterTextView: EditText? = null
private var otpDigitsContainer: TextInputLayout? = null
private var otpDigitsTextView: EditText? = null
private var otpAlgorithmSpinner: Spinner? = null
private var otpTypeAdapter: ArrayAdapter<OtpType>? = null
private var otpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
private var mManualEvent = false
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
if (!isFocus)
mManualEvent = true
}
private var mOnTouchListener = View.OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mManualEvent = true
}
}
false
}
private var mSecretWellFormed = false
private var mCounterWellFormed = true
private var mPeriodWellFormed = true
private var mDigitsWellFormed = true
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mCreateOTPElementListener = context as CreateOtpListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + CreateOtpListener::class.java.name)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Retrieve OTP model from instance state
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_OTP)) {
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
} else {
arguments?.apply {
if (containsKey(KEY_OTP)) {
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
}
}
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
otpSecretTextView = root?.findViewById(R.id.setup_otp_secret)
otpAlgorithmSpinner = root?.findViewById(R.id.setup_otp_algorithm)
otpPeriodContainer= root?.findViewById(R.id.setup_otp_period_label)
otpPeriodTextView = root?.findViewById(R.id.setup_otp_period)
otpCounterContainer= root?.findViewById(R.id.setup_otp_counter_label)
otpCounterTextView = root?.findViewById(R.id.setup_otp_counter)
otpDigitsContainer = root?.findViewById(R.id.setup_otp_digits_label)
otpDigitsTextView = root?.findViewById(R.id.setup_otp_digits)
// To fix init element
// With tab keyboard selection
otpSecretTextView?.onFocusChangeListener = mOnFocusChangeListener
// With finger selection
otpTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpTokenTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpSecretTextView?.setOnTouchListener(mOnTouchListener)
otpAlgorithmSpinner?.setOnTouchListener(mOnTouchListener)
otpPeriodTextView?.setOnTouchListener(mOnTouchListener)
otpCounterTextView?.setOnTouchListener(mOnTouchListener)
otpDigitsTextView?.setOnTouchListener(mOnTouchListener)
// HOTP / TOTP Type selection
val otpTypeArray = OtpType.values()
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
android.R.layout.simple_spinner_item, otpTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTypeSpinner?.adapter = otpTypeAdapter
// Otp Token type selection
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
hotpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
// Proprietary only on closed and full version
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTokenTypeAdapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.adapter = otpTokenTypeAdapter
// OTP Algorithm
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpAlgorithmSpinner?.adapter = otpAlgorithmAdapter
// Set the default value of OTP element
upgradeType()
upgradeTokenType()
upgradeParameters()
attachListeners()
val builder = AlertDialog.Builder(activity)
builder.apply {
setTitle(R.string.entry_setup_otp)
setView(root)
.setPositiveButton(android.R.string.ok) {_, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
(dialog as AlertDialog).getButton(Dialog.BUTTON_POSITIVE).setOnClickListener {
if (mSecretWellFormed
&& mCounterWellFormed
&& mPeriodWellFormed
&& mDigitsWellFormed) {
mCreateOTPElementListener?.onOtpCreated(mOtpElement)
dismiss()
}
}
}
private fun attachListeners() {
// Set Type listener
otpTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpType?)?.let {
mOtpElement.type = it
upgradeTokenType()
}
}
}
}
// Set type token listener
otpTokenTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpTokenType?)?.let {
mOtpElement.tokenType = it
upgradeParameters()
}
}
}
}
// Set algorithm spinner
otpAlgorithmSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as TokenCalculator.HashAlgorithm?)?.let {
mOtpElement.algorithm = it
}
}
}
}
// Set secret in OtpElement
otpSecretTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
s?.toString()?.let { userString ->
try {
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
otpSecretContainer?.error = null
} catch (exception: Exception) {
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)
}
mSecretWellFormed = otpSecretContainer?.error == null
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set counter in OtpElement
otpCounterTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toLongOrNull()?.let {
try {
mOtpElement.counter = it
otpCounterContainer?.error = null
} catch (exception: Exception) {
otpCounterContainer?.error = getString(R.string.error_otp_counter,
MIN_HOTP_COUNTER, MAX_HOTP_COUNTER)
}
mCounterWellFormed = otpCounterContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set period in OtpElement
otpPeriodTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.period = it
otpPeriodContainer?.error = null
} catch (exception: Exception) {
otpPeriodContainer?.error = getString(R.string.error_otp_period,
MIN_TOTP_PERIOD, MAX_TOTP_PERIOD)
}
mPeriodWellFormed = otpPeriodContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set digits in OtpElement
otpDigitsTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.digits = it
otpDigitsContainer?.error = null
} catch (exception: Exception) {
otpDigitsContainer?.error = getString(R.string.error_otp_digits,
MIN_OTP_DIGITS, MAX_OTP_DIGITS)
}
mDigitsWellFormed = otpDigitsContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
}
private fun upgradeType() {
otpTypeSpinner?.setSelection(OtpType.values().indexOf(mOtpElement.type))
}
private fun upgradeTokenType() {
when (mOtpElement.type) {
OtpType.HOTP -> {
otpPeriodContainer?.visibility = View.GONE
otpCounterContainer?.visibility = View.VISIBLE
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
OtpType.TOTP -> {
otpPeriodContainer?.visibility = View.VISIBLE
otpCounterContainer?.visibility = View.GONE
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
}
}
private fun upgradeParameters() {
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
.indexOf(mOtpElement.algorithm))
otpSecretTextView?.apply {
setText(mOtpElement.getBase32Secret())
// Cursor at end
setSelection(this.text.length)
}
otpCounterTextView?.setText(mOtpElement.counter.toString())
otpPeriodTextView?.setText(mOtpElement.period.toString())
otpDigitsTextView?.setText(mOtpElement.digits.toString())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(KEY_OTP, mOtpElement.otpModel)
}
interface CreateOtpListener {
fun onOtpCreated(otpElement: OtpElement)
}
companion object {
private const val KEY_OTP = "KEY_OTP"
fun build(otpModel: OtpModel? = null): SetOTPDialogFragment {
return SetOTPDialogFragment().apply {
if (otpModel != null) {
arguments = Bundle().apply {
putParcelable(KEY_OTP, otpModel)
}
}
}
}
}
}

View File

@@ -29,7 +29,7 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.SortNodeEnum
class SortDialogFragment : DialogFragment() {

View File

@@ -75,10 +75,9 @@ class OpenFileHelper {
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
@@ -86,15 +85,10 @@ class OpenFileHelper {
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)

View File

@@ -32,11 +32,9 @@ import android.view.ViewGroup
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LOCK_ACTION
@@ -64,10 +62,6 @@ abstract class LockingActivity : StylishActivity() {
return field || mSelectionMode
}
protected var mSelectionMode: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDialogThread: ProgressDialogThread? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -91,8 +85,6 @@ abstract class LockingActivity : StylishActivity() {
mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -108,13 +100,8 @@ abstract class LockingActivity : StylishActivity() {
override fun onResume() {
super.onResume()
mProgressDialogThread?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
if (mTimeoutEnable) {
// End activity if database not loaded
@@ -131,6 +118,8 @@ abstract class LockingActivity : StylishActivity() {
if (!mExitLock)
TimeoutHelper.recordTime(this)
}
invalidateOptionsMenu()
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -140,8 +129,6 @@ abstract class LockingActivity : StylishActivity() {
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
if (mTimeoutEnable) {
@@ -212,9 +199,6 @@ fun Activity.lock() {
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
MagikIME.removeEntry(this)
// Stop the notification service
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
" after inactivity or manual lock")
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {

View File

@@ -7,13 +7,13 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.EntryVersioned
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryHistoryList: MutableList<Entry> = ArrayList()
var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
var entryHistoryList: MutableList<EntryVersioned> = ArrayList()
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))

View File

@@ -18,6 +18,7 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
return FieldViewHolder(view)

View File

@@ -34,10 +34,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
@@ -50,7 +48,7 @@ class NodeAdapter
(private val context: Context)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
private val nodeSortedList: SortedList<Node>
private val nodeSortedList: SortedList<NodeVersioned>
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
@@ -67,7 +65,7 @@ class NodeAdapter
private var showUserNames: Boolean = true
private var showNumberEntries: Boolean = true
private var actionNodesList = LinkedList<Node>()
private var actionNodesList = LinkedList<NodeVersioned>()
private var nodeClickCallback: NodeClickCallback? = null
private val mDatabase: Database
@@ -85,18 +83,18 @@ class NodeAdapter
init {
assignPreferences()
this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
return item1 == item2
}
})
@@ -135,7 +133,7 @@ class NodeAdapter
/**
* Rebuild the list by clear and build children from the group
*/
fun rebuildList(group: Group) {
fun rebuildList(group: GroupVersioned) {
this.nodeSortedList.clear()
assignPreferences()
try {
@@ -147,7 +145,7 @@ class NodeAdapter
notifyDataSetChanged()
}
fun contains(node: Node): Boolean {
fun contains(node: NodeVersioned): Boolean {
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
}
@@ -155,7 +153,7 @@ class NodeAdapter
* Add a node to the list
* @param node Node to add
*/
fun addNode(node: Node) {
fun addNode(node: NodeVersioned) {
nodeSortedList.add(node)
}
@@ -163,7 +161,7 @@ class NodeAdapter
* Add nodes to the list
* @param nodes Nodes to add
*/
fun addNodes(nodes: List<Node>) {
fun addNodes(nodes: List<NodeVersioned>) {
nodeSortedList.addAll(nodes)
}
@@ -171,7 +169,7 @@ class NodeAdapter
* Remove a node in the list
* @param node Node to delete
*/
fun removeNode(node: Node) {
fun removeNode(node: NodeVersioned) {
nodeSortedList.remove(node)
}
@@ -179,7 +177,7 @@ class NodeAdapter
* Remove nodes in the list
* @param nodes Nodes to delete
*/
fun removeNodes(nodes: List<Node>) {
fun removeNodes(nodes: List<NodeVersioned>) {
nodes.forEach { node ->
nodeSortedList.remove(node)
}
@@ -211,7 +209,7 @@ class NodeAdapter
* @param oldNode Node before the update
* @param newNode Node after the update
*/
fun updateNode(oldNode: Node, newNode: Node) {
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
nodeSortedList.beginBatchedUpdates()
nodeSortedList.remove(oldNode)
nodeSortedList.add(newNode)
@@ -223,7 +221,7 @@ class NodeAdapter
* @param oldNodes Nodes before the update
* @param newNodes Node after the update
*/
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
nodeSortedList.beginBatchedUpdates()
oldNodes.forEach { oldNode ->
nodeSortedList.remove(oldNode)
@@ -232,11 +230,11 @@ class NodeAdapter
nodeSortedList.endBatchedUpdates()
}
fun notifyNodeChanged(node: Node) {
fun notifyNodeChanged(node: NodeVersioned) {
notifyItemChanged(nodeSortedList.indexOf(node))
}
fun setActionNodes(actionNodes: List<Node>) {
fun setActionNodes(actionNodes: List<NodeVersioned>) {
this.actionNodesList.apply {
clear()
addAll(actionNodes)
@@ -317,7 +315,7 @@ class NodeAdapter
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
val entry = subNode as EntryVersioned
mDatabase.startManageEntry(entry)
@@ -338,7 +336,7 @@ class NodeAdapter
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group).getChildEntries(true).size.toString()
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
@@ -363,8 +361,8 @@ class NodeAdapter
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickCallback {
fun onNodeClick(node: Node)
fun onNodeLongClick(node: Node): Boolean
fun onNodeClick(node: NodeVersioned)
fun onNodeLongClick(node: NodeVersioned): Boolean
}
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import androidx.cursoradapter.widget.CursorAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -30,8 +31,8 @@ import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
@@ -75,7 +76,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
val iconFactory = database.iconFactory
var icon: IconImage = iconFactory.getIcon(
var icon: PwIcon = iconFactory.getIcon(
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
if (icon.isUnknown) {
@@ -93,7 +94,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
// Assign title
val showTitle = Entry.getVisualTitle(false, title, username, url, uuid.toString())
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())
viewHolder.textViewTitle?.text = showTitle
if (displayUsername && username.isNotEmpty()) {
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
@@ -112,8 +113,8 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
return database.searchEntries(constraint.toString())
}
fun getEntryFromPosition(position: Int): Entry? {
var pwEntry: Entry? = null
fun getEntryFromPosition(position: Int): EntryVersioned? {
var pwEntry: EntryVersioned? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {

View File

@@ -1,16 +1,13 @@
package com.kunzisoft.keepass.biometric
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
@@ -22,12 +19,12 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockedManager(var context: FragmentActivity,
var databaseFileUri: Uri,
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
private var checkboxPasswordView: CompoundButton?,
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var advancedUnlockInfoView: AdvancedUnlockInfoView?,
var checkboxPasswordView: CompoundButton?,
var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var passwordView: TextView?,
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
@@ -37,59 +34,65 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
init {
// fingerprint related code here
fun initBiometric() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isBiometricInitialized) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback)
}
// Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
}
checkBiometricAvailability()
}
/**
* Check biometric availability and change the current mode depending of device's state
*/
@Synchronized
fun checkBiometricAvailability() {
// biometric not supported (by API level or hardware) so keep option hidden
// fingerprint not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.UNAVAILABLE)
} else {
// biometric 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
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
toggleMode(Mode.NOT_CONFIGURED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE)
} else {
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode( if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
// fingerprint available but no stored password found yet for this DB so show info don't listen
toggleMode( if (it) {
// listen for decryption
Mode.OPEN
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
}
}
@@ -103,7 +106,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
}
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () {
override fun onAuthenticationError(
errorCode: Int,
@@ -124,15 +127,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread {
when (biometricMode) {
Mode.UNAVAILABLE -> {}
Mode.BIOMETRIC_NOT_CONFIGURED -> {}
Mode.KEY_MANAGER_UNAVAILABLE -> {}
Mode.WAIT_CREDENTIAL -> {}
Mode.STORE_CREDENTIAL -> {
Mode.UNAVAILABLE -> {
}
Mode.PAUSE -> {
}
Mode.NOT_CONFIGURED -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE -> {
// newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
}
Mode.EXTRACT_CREDENTIAL -> {
Mode.OPEN -> {
// retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
it?.encryptedValue?.let { value ->
@@ -148,7 +155,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private fun initNotAvailable() {
showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(false, null)
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initPause() {
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initNotConfigured() {
@@ -156,19 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
}
private fun initKeyManagerNotAvailable() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initWaitData() {
@@ -176,19 +175,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
biometricAuthenticationCallback.onAuthenticationError(
BiometricConstants.ERROR_UNABLE_TO_PROCESS
, context.getString(R.string.credential_before_click_biometric_button))
}
}
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject,
promptInfo: BiometricPrompt.PromptInfo) {
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, cryptoObject)
}
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initEncryptData() {
@@ -201,7 +188,9 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and save credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
}
@@ -222,13 +211,17 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and check credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
// Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) {
isBiometricPromptAutoOpenEnable = false
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
}
@@ -242,19 +235,28 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun initBiometricMode() {
when (biometricMode) {
Mode.UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.PAUSE -> initPause()
Mode.NOT_CONFIGURED -> initNotConfigured()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
Mode.STORE -> initEncryptData()
Mode.OPEN -> initDecryptData()
}
// Show fingerprint key deletion
context.invalidateOptionsMenu()
}
fun pause() {
biometricMode = Mode.PAUSE
initBiometricMode()
}
fun destroy() {
// Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
biometricMode = Mode.UNAVAILABLE
initBiometricMode()
biometricUnlockDatabaseHelper = null
}
// Only to fix multiple fingerprint menu #332
@@ -263,7 +265,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
if (!addBiometricMenuInProgress) {
addBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED)
&& it) {
menuInflater.inflate(R.menu.advanced_unlock, menu)
addBiometricMenuInProgress = false
@@ -275,7 +277,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun deleteEntryKey() {
biometricUnlockDatabaseHelper?.deleteEntryKey()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
biometricMode = Mode.NOT_CONFIGURED
checkBiometricAvailability()
}
@@ -321,7 +323,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
enum class Mode {
UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN
}
companion object {

View File

@@ -42,7 +42,8 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M)
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
private val biometricUnlockCallback: BiometricUnlockCallback) {
private var biometricPrompt: BiometricPrompt? = null
@@ -52,14 +53,12 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null
private var isKeyManagerInit = false
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private var isBiometricInit = false
private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
setConfirmationRequired(true)
// TODO device credential
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -71,8 +70,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
setConfirmationRequired(false)
setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
// TODO device credential
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -82,18 +80,18 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setNegativeButtonText(context.getString(android.R.string.cancel))
}.build()
val isKeyManagerInitialized: Boolean
val isBiometricInitialized: Boolean
get() {
if (!isKeyManagerInit) {
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
if (!isBiometricInit) {
biometricUnlockCallback.onBiometricException(Exception("Biometric not initialized"))
}
return isKeyManagerInit
return isBiometricInit
}
init {
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
// really not much to do when no fingerprint support found
isKeyManagerInit = false
isBiometricInit = false
} else {
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
@@ -105,19 +103,17 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
+ BIOMETRIC_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
isKeyManagerInit = (keyStore != null
&& keyGenerator != null
&& cipher != null)
isBiometricInit = true
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e)
isKeyManagerInit = false
biometricUnlockCallback?.onBiometricException(e)
isBiometricInit = false
biometricUnlockCallback.onBiometricException(e)
}
}
}
private fun getSecretKey(): SecretKey? {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return null
}
try {
@@ -143,14 +139,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
}
} catch (e: Exception) {
Log.e(TAG, "Unable to create a key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve the key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
return null
}
@@ -159,7 +155,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -172,18 +168,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
biometricUnlockCallback.onInvalidKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun encryptData(value: String) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -193,20 +190,21 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
biometricUnlockCallback.handleEncryptedResult(encryptedBase64, ivSpecValue)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -226,30 +224,32 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
deleteEntryKey()
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun decryptData(encryptedValue: String) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
// actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted ->
biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
biometricUnlockCallback.handleDecryptedResult(String(decrypted))
}
} catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException)
biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
biometricUnlockCallback.onInvalidKeyException(badPaddingException)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun deleteEntryKey() {
@@ -258,10 +258,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
} catch (e: Exception) {
Log.e(TAG, "Unable to delete entry key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) {
this.authenticationCallback = authenticationCallback
}
@Synchronized
fun initBiometricPrompt() {
if (biometricPrompt == null) {
@@ -295,24 +299,22 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
* Remove entry key in keystore
*/
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricCallback: BiometricUnlockErrorCallback) {
BiometricUnlockDatabaseHelper(context).apply {
biometricUnlockCallback = object : BiometricUnlockCallback {
biometricUnlockCallback: BiometricUnlockErrorCallback) {
val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback {
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun onInvalidKeyException(e: Exception) {
biometricCallback.onInvalidKeyException(e)
}
override fun onBiometricException(e: Exception) {
biometricCallback.onBiometricException(e)
}
override fun onInvalidKeyException(e: Exception) {
biometricUnlockCallback.onInvalidKeyException(e)
}
deleteEntryKey()
}
override fun onBiometricException(e: Exception) {
biometricUnlockCallback.onBiometricException(e)
}
})
fingerPrintHelper.deleteEntryKey()
}
}

View File

@@ -20,44 +20,43 @@
package com.kunzisoft.keepass.biometric
import android.content.Context
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RequiresApi
import android.widget.ImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.kunzisoft.keepass.R
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
private val scanFingerprint: AnimatedVectorDrawableCompat? =
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
private val scanFingerprint: AnimatedVectorDrawable =
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable
init {
imageView.setImageDrawable(scanFingerprint)
}
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
private var animationCallback = object : Animatable2.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) {
imageView.post {
scanFingerprint?.start()
}
if (!scanFingerprint.isRunning)
scanFingerprint.start()
}
}
fun startScan() {
scanFingerprint?.registerAnimationCallback(animationCallback)
scanFingerprint.registerAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning != true)
scanFingerprint?.start()
if (!scanFingerprint.isRunning)
scanFingerprint.start()
}
fun stopScan() {
scanFingerprint?.unregisterAnimationCallback(animationCallback)
scanFingerprint.unregisterAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning == true)
if (scanFingerprint.isRunning)
scanFingerprint.stop()
}
}

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
@@ -41,13 +41,13 @@ class AesEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.AESRijndael
}
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte()))
}
}

View File

@@ -19,8 +19,8 @@
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -44,13 +44,13 @@ class ChaCha20Engine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.ChaCha20
}
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte()))
}
}

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -46,6 +46,6 @@ abstract class CipherEngine {
return getCipher(opmode, key, IV, false)
}
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm
}

View File

@@ -20,8 +20,8 @@
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -47,13 +47,13 @@ class TwofishEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.Twofish
}
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte()))
}
}

View File

@@ -23,7 +23,7 @@ import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.utils.Types
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid!!).apply {
return KdfParameters(uuid).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
}
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.utils.Types
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
val p = KdfParameters(uuid!!)
val p = KdfParameters(uuid)
p.setParamUUID()
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.database.ObjectNameResource
import java.io.IOException
import java.io.Serializable

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.utils.VariantDictionary;
import com.kunzisoft.keepass.stream.LEDataInputStream;
import com.kunzisoft.keepass.stream.LEDataOutputStream;
import com.kunzisoft.keepass.utils.Types;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.UUID;
public class KdfParameters extends VariantDictionary {
private UUID kdfUUID;
private static final String ParamUUID = "$UUID";
KdfParameters(UUID uuid) {
kdfUUID = uuid;
}
public UUID getUUID() {
return kdfUUID;
}
protected void setParamUUID() {
setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID));
}
public static KdfParameters deserialize(byte[] data) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
LEDataInputStream lis = new LEDataInputStream(bis);
VariantDictionary d = VariantDictionary.deserialize(lis);
if (d == null) {
return null;
}
UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID));
KdfParameters kdfP = new KdfParameters(uuid);
kdfP.copyTo(d);
return kdfP;
}
public static byte[] serialize(KdfParameters kdf) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
LEDataOutputStream los = new LEDataOutputStream(bos);
KdfParameters.serialize(kdf, los);
return bos.toByteArray();
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.UUID
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, DatabaseInputOutputUtils.uuidToBytes(uuid))
}
companion object {
private const val PARAM_UUID = "\$UUID"
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LEDataInputStream(bis)
val d = deserialize(lis) ?: return null
val uuid = DatabaseInputOutputUtils.bytesToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
return kdfP
}
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LEDataOutputStream(bos)
serialize(kdf, los)
return bos.toByteArray()
}
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
package com.kunzisoft.keepass.database
/** "Delegate" class for operating on each group when traversing all of

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
package com.kunzisoft.keepass.database
import android.content.res.Resources

View File

@@ -18,16 +18,18 @@
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Type
import java.util.*
enum class SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<Node> {
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<NodeVersioned> {
return when (this) {
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
@@ -38,11 +40,11 @@ enum class SortNodeEnum {
}
}
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<Node> {
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<NodeVersioned> {
abstract fun compareBySpecificOrder(object1: Node, object2: Node): Int
abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int
private fun specificOrderOrHashIfEquals(object1: Node, object2: Node): Int {
private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return if (specificOrderComp == 0) {
@@ -50,20 +52,20 @@ enum class SortNodeEnum {
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
}
override fun compare(object1: Node, object2: Node): Int {
override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int {
if (object1 == object2)
return 0
if (object1.type == Type.GROUP) {
return if (object2.type == Type.GROUP) {
// RecycleBin at end of groups
val database = Database.getInstance()
if (database.isRecycleBinEnabled && recycleBinBottom) {
if (database.recycleBin == object1)
if (recycleBinBottom) {
if (Database.getInstance().recycleBin == object1)
return 1
if (database.recycleBin == object2)
if (Database.getInstance().recycleBin == object2)
return -1
}
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.ENTRY) {
if (groupsBefore)
@@ -97,7 +99,7 @@ enum class SortNodeEnum {
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
}
}
@@ -108,7 +110,7 @@ enum class SortNodeEnum {
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.title.compareTo(object2.title, ignoreCase = true)
}
}
@@ -119,11 +121,11 @@ enum class SortNodeEnum {
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
// To get username if it's a ref
return (object1 as Entry).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as Entry).getEntryInfo(Database.getInstance()).username,
return (object1 as EntryVersioned).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as EntryVersioned).getEntryInfo(Database.getInstance()).username,
ignoreCase = true)
}
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
@@ -136,7 +138,7 @@ enum class SortNodeEnum {
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.creationTime.date
.compareTo(object2.creationTime.date)
}
@@ -148,7 +150,7 @@ enum class SortNodeEnum {
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
}
@@ -160,7 +162,7 @@ enum class SortNodeEnum {
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
}

View File

@@ -21,22 +21,23 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.UriUtil
open class AssignPasswordInDatabaseRunnable (
open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?)
: SaveDatabaseRunnable(context, database, true) {
keyFile: Uri?,
save: Boolean,
actionRunnable: ActionRunnable? = null)
: SaveDatabaseRunnable(context, database, save, actionRunnable) {
private var mMasterPassword: String? = null
protected var mKeyFile: Uri? = null
private var mKeyFile: Uri? = null
private var mBackupKey: ByteArray? = null
@@ -47,7 +48,7 @@ open class AssignPasswordInDatabaseRunnable (
this.mKeyFile = keyFile
}
override fun onStartRun() {
override fun run() {
// Set key
try {
// TODO move master key methods
@@ -56,21 +57,17 @@ open class AssignPasswordInDatabaseRunnable (
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
database.retrieveMasterKey(mMasterPassword, uriInputStream)
// To save the database
super.run()
finishRun(true)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)
finishRun(false, e.message)
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
// Erase the biometric
CipherDatabaseAction.getInstance(context)
.deleteByDatabaseUri(mDatabaseUri)
override fun onFinishRun(result: Result) {
if (!result.isSuccess) {
// Erase the current master key
erase(database.masterKey)
@@ -78,6 +75,8 @@ open class AssignPasswordInDatabaseRunnable (
database.masterKey = it
}
}
super.onFinishRun(result)
}
/**

View File

@@ -21,46 +21,38 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
class CreateDatabaseRunnable(context: Context,
private val mDatabaseUri: Uri,
private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?)
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
keyFile: Uri?,
save: Boolean,
actionRunnable: ActionRunnable? = null)
: AssignPasswordInDatabaseRunnable(context, mDatabase, withMasterPassword, masterPassword, withKeyFile, keyFile, save, actionRunnable) {
override fun onStartRun() {
override fun run() {
try {
// Create new database record
mDatabase.apply {
createData(mDatabaseUri, databaseName, rootName)
createData(mDatabaseUri)
// Set Database state
loaded = true
// Commit changes
super.run()
}
finishRun(true)
} catch (e: Exception) {
mDatabase.closeAndClear()
setError(e)
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (result.isSuccess) {
// Add database to recent files
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri, mKeyFile)
} else {
Log.e("CreateDatabaseRunnable", "Unable to create the database")
finishRun(false, e.message)
}
}
override fun onFinishRun(result: Result) {}
}

View File

@@ -25,9 +25,7 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -42,18 +40,16 @@ class LoadDatabaseRunnable(private val context: Context,
private val mOmitBackup: Boolean,
private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mDuplicateUuidAction: ((Result) -> Unit)?)
: ActionRunnable() {
actionFinishRunnable: ActionRunnable?)
: ActionRunnable(actionFinishRunnable, executeNestedActionIfResultFalse = true) {
private val cacheDirectory = context.applicationContext.filesDir
override fun onStartRun() {
// Clear before we load
mDatabase.closeAndClear(cacheDirectory)
}
override fun onActionRun() {
override fun run() {
try {
// Clear before we load
mDatabase.closeAndClear(cacheDirectory)
mDatabase.loadData(mUri, mPass, mKey,
mReadonly,
context.contentResolver,
@@ -61,18 +57,7 @@ class LoadDatabaseRunnable(private val context: Context,
mOmitBackup,
mFixDuplicateUUID,
progressTaskUpdater)
}
catch (e: DuplicateUuidDatabaseException) {
mDuplicateUuidAction?.invoke(result)
setError(e)
}
catch (e: LoadDatabaseException) {
setError(e)
}
}
override fun onFinishRun() {
if (result.isSuccess) {
// Save keyFile in app database
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context)
if (rememberKeyFile) {
@@ -87,12 +72,20 @@ class LoadDatabaseRunnable(private val context: Context,
// Register the biometric
mCipherEntity?.let { cipherDatabaseEntity ->
CipherDatabaseAction.getInstance(context)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
finishRun(true)
}
} ?: run {
finishRun(true)
}
}
catch (e: LoadDatabaseException) {
finishRun(false, e)
}
}
// Start the opening notification
DatabaseOpenNotificationService.startIfAllowed(context)
} else {
override fun onFinishRun(result: Result) {
if (!result.isSuccess) {
mDatabase.closeAndClear(cacheDirectory)
}
}

View File

@@ -7,15 +7,11 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.view.View
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
@@ -25,19 +21,18 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COMPRESSION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
@@ -50,10 +45,10 @@ import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import java.util.*
import kotlin.collections.ArrayList
class ProgressDialogThread(private val activity: FragmentActivity) {
var onActionFinish: ((actionTask: String,
result: ActionRunnable.Result) -> Unit)? = null
class ProgressDialogThread(private val activity: FragmentActivity,
var onActionFinish: (actionTask: String,
result: ActionRunnable.Result) -> Unit) {
private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java)
@@ -64,24 +59,29 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
TimeoutHelper.temporarilyDisableTimeout(activity)
startOrUpdateDialog(titleId, messageId, warningId)
TimeoutHelper.temporarilyDisableTimeout()
startOrUpdateDialog(titleId, messageId, warningId, View.OnClickListener {
mBinder?.cancelTask()
})
}
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
TimeoutHelper.temporarilyDisableTimeout(activity)
startOrUpdateDialog(titleId, messageId, warningId)
TimeoutHelper.temporarilyDisableTimeout()
startOrUpdateDialog(titleId, messageId, warningId, View.OnClickListener {
mBinder?.cancelTask()
})
}
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result)
onActionFinish.invoke(actionTask, result)
// Remove the progress task
ProgressTaskDialogFragment.stop(activity)
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity)
}
}
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?,
cancelableListener: View.OnClickListener?) {
var progressTaskDialogFragment = retrieveProgressDialog(activity)
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment.build()
@@ -97,6 +97,7 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
warningId?.let {
updateWarning(it)
}
setCancelButton(cancelableListener)
}
}
@@ -234,14 +235,12 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_LOAD_TASK)
}
fun startDatabaseAssignPassword(databaseUri: Uri,
masterPasswordChecked: Boolean,
fun startDatabaseAssignPassword(masterPasswordChecked: Boolean,
masterPassword: String?,
keyFileChecked: Boolean,
keyFile: Uri?) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
@@ -256,8 +255,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
----
*/
fun startDatabaseCreateGroup(newGroup: Group,
parent: Group,
fun startDatabaseCreateGroup(newGroup: GroupVersioned,
parent: GroupVersioned,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup)
@@ -267,8 +266,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_CREATE_GROUP_TASK)
}
fun startDatabaseUpdateGroup(oldGroup: Group,
groupToUpdate: Group,
fun startDatabaseUpdateGroup(oldGroup: GroupVersioned,
groupToUpdate: GroupVersioned,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId)
@@ -278,8 +277,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_UPDATE_GROUP_TASK)
}
fun startDatabaseCreateEntry(newEntry: Entry,
parent: Group,
fun startDatabaseCreateEntry(newEntry: EntryVersioned,
parent: GroupVersioned,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry)
@@ -289,8 +288,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_CREATE_ENTRY_TASK)
}
fun startDatabaseUpdateEntry(oldEntry: Entry,
entryToUpdate: Entry,
fun startDatabaseUpdateEntry(oldEntry: EntryVersioned,
entryToUpdate: EntryVersioned,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId)
@@ -301,20 +300,20 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
}
private fun startDatabaseActionListNodes(actionTask: String,
nodesPaste: List<Node>,
newParent: Group?,
nodesPaste: List<NodeVersioned>,
newParent: GroupVersioned?,
save: Boolean) {
val groupsIdToCopy = ArrayList<NodeId<*>>()
val entriesIdToCopy = ArrayList<NodeId<UUID>>()
val groupsIdToCopy = ArrayList<PwNodeId<*>>()
val entriesIdToCopy = ArrayList<PwNodeId<UUID>>()
nodesPaste.forEach { nodeVersioned ->
when (nodeVersioned.type) {
Type.GROUP -> {
(nodeVersioned as Group).nodeId?.let { groupId ->
(nodeVersioned as GroupVersioned).nodeId?.let { groupId ->
groupsIdToCopy.add(groupId)
}
}
Type.ENTRY -> {
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId)
}
}
}
@@ -331,19 +330,19 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, actionTask)
}
fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
newParent: Group,
fun startDatabaseCopyNodes(nodesToCopy: List<NodeVersioned>,
newParent: GroupVersioned,
save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save)
}
fun startDatabaseMoveNodes(nodesToMove: List<Node>,
newParent: Group,
fun startDatabaseMoveNodes(nodesToMove: List<NodeVersioned>,
newParent: GroupVersioned,
save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save)
}
fun startDatabaseDeleteNodes(nodesToDelete: List<Node>,
fun startDatabaseDeleteNodes(nodesToDelete: List<NodeVersioned>,
save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
}
@@ -355,80 +354,66 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
*/
fun startDatabaseSaveName(oldName: String,
newName: String,
save: Boolean) {
newName: String) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_NAME_TASK)
, ACTION_DATABASE_SAVE_NAME_TASK)
}
fun startDatabaseSaveDescription(oldDescription: String,
newDescription: String,
save: Boolean) {
newDescription: String) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK)
, ACTION_DATABASE_SAVE_DESCRIPTION_TASK)
}
fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String,
newDefaultUsername: String,
save: Boolean) {
newDefaultUsername: String) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK)
, ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK)
}
fun startDatabaseSaveColor(oldColor: String,
newColor: String,
save: Boolean) {
newColor: String) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_COLOR_TASK)
, ACTION_DATABASE_SAVE_COLOR_TASK)
}
fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm,
save: Boolean) {
fun startDatabaseSaveCompression(oldCompression: PwCompressionAlgorithm,
newCompression: PwCompressionAlgorithm) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
, ACTION_DATABASE_SAVE_COMPRESSION_TASK)
}
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
newMaxHistoryItems: Int,
save: Boolean) {
newMaxHistoryItems: Int) {
start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK)
, ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK)
}
fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long,
newMaxHistorySize: Long,
save: Boolean) {
newMaxHistorySize: Long) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK)
, ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK)
}
/*
@@ -437,68 +422,48 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
-------------------
*/
fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm,
newEncryption: EncryptionAlgorithm,
save: Boolean) {
fun startDatabaseSaveEncryption(oldEncryption: PwEncryptionAlgorithm,
newEncryption: PwEncryptionAlgorithm) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK)
, ACTION_DATABASE_SAVE_ENCRYPTION_TASK)
}
fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine,
newKeyDerivation: KdfEngine,
save: Boolean) {
newKeyDerivation: KdfEngine) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK)
, ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK)
}
fun startDatabaseSaveIterations(oldIterations: Long,
newIterations: Long,
save: Boolean) {
newIterations: Long) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_ITERATIONS_TASK)
, ACTION_DATABASE_SAVE_ITERATIONS_TASK)
}
fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long,
newMemoryUsage: Long,
save: Boolean) {
newMemoryUsage: Long) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
, ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK)
}
fun startDatabaseSaveParallelism(oldParallelism: Int,
newParallelism: Int,
save: Boolean) {
newParallelism: Int) {
start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
}
/**
* Save Database without parameter
*/
fun startDatabaseSave(save: Boolean) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_SAVE)
, ACTION_DATABASE_SAVE_PARALLELISM_TASK)
}
}

View File

@@ -21,30 +21,43 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.tasks.ActionRunnable
import java.io.IOException
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
private var saveDatabase: Boolean)
: ActionRunnable() {
abstract class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
protected var saveDatabase: Boolean,
nestedAction: ActionRunnable? = null)
: ActionRunnable(nestedAction) {
var mAfterSaveDatabase: ((Result) -> Unit)? = null
override fun onStartRun() {}
override fun onActionRun() {
if (saveDatabase && result.isSuccess) {
override fun run() {
if (saveDatabase) {
try {
database.saveData(context.contentResolver)
} catch (e: DatabaseException) {
setError(e)
} catch (e: IOException) {
finishRun(false, e.message)
} catch (e: DatabaseOutputException) {
finishRun(false, e.message)
}
}
// Need to call super.run() in child class
}
override fun onFinishRun() {
// Need to call super.onFinishRun() in child class
mAfterSaveDatabase?.invoke(result)
override fun onFinishRun(result: Result) {
// Need to call super.onFinishRun(result) in child class
}
}
class SaveDatabaseActionRunnable(context: Context,
database: Database,
save: Boolean,
nestedAction: ActionRunnable? = null)
: SaveDatabaseRunnable(context, database, save, nestedAction) {
override fun run() {
super.run()
finishRun(true)
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
class UpdateCompressionBinariesDatabaseRunnable (
context: Context,
database: Database,
private val oldCompressionAlgorithm: CompressionAlgorithm,
private val newCompressionAlgorithm: CompressionAlgorithm,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
// Set new compression
if (database.allowDataCompression) {
try {
database.apply {
updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm)
compressionAlgorithm = newCompressionAlgorithm
}
} catch (e: Exception) {
setError(e)
}
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (database.allowDataCompression) {
if (!result.isSuccess) {
try {
database.apply {
compressionAlgorithm = oldCompressionAlgorithm
updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm)
}
} catch (e: Exception) {
setError(e)
}
}
}
}
}

View File

@@ -3,33 +3,44 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
abstract class ActionNodeDatabaseRunnable(
context: Context,
database: Database,
private val afterActionNodesFinish: AfterActionNodesFinish?,
private val callbackRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: SaveDatabaseRunnable(context, database, save) {
/**
* Function do to a node action
* Function do to a node action, don't implements run() if used this
*/
abstract fun nodeAction()
override fun onStartRun() {
protected fun saveDatabaseAndFinish() {
if (result.isSuccess) {
super.run()
finishRun(true)
}
}
protected fun throwErrorAndFinish(throwable: LoadDatabaseException) {
finishRun(false, throwable)
}
override fun run() {
nodeAction()
super.onStartRun()
}
/**
* Function do get the finish node action
* Function do get the finish node action, don't implements onFinishRun() if used this
*/
abstract fun nodeFinish(): ActionNodesValues
abstract fun nodeFinish(result: Result): ActionNodeValues
override fun onFinishRun() {
super.onFinishRun()
afterActionNodesFinish?.apply {
onActionNodesFinish(result, nodeFinish())
override fun onFinishRun(result: Result) {
callbackRunnable?.apply {
onActionNodeFinish(nodeFinish(result))
}
super.onFinishRun(result)
}
}

View File

@@ -21,35 +21,36 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class AddEntryRunnable constructor(
context: Context,
database: Database,
private val mNewEntry: Entry,
private val mParent: Group,
private val mNewEntry: EntryVersioned,
private val mParent: GroupVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
finishRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, finishRunnable, save) {
override fun nodeAction() {
mNewEntry.touch(modified = true, touchParents = true)
mParent.touch(modified = true, touchParents = true)
database.addEntryTo(mNewEntry, mParent)
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
mNewEntry.parent?.let {
database.removeEntryFrom(mNewEntry, it)
}
}
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
}
}

View File

@@ -21,32 +21,33 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class AddGroupRunnable constructor(
context: Context,
database: Database,
private val mNewGroup: Group,
private val mParent: Group,
private val mNewGroup: GroupVersioned,
private val mParent: GroupVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterAddNodeRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
override fun nodeAction() {
mNewGroup.touch(modified = true, touchParents = true)
mParent.touch(modified = true, touchParents = true)
database.addGroupTo(mNewGroup, mParent)
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
database.removeGroupFrom(mNewGroup, mParent)
}
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
}
}

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.tasks.ActionRunnable
/**
@@ -30,8 +30,8 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
* - Move : @param oldNodes empty, @param newNodes NodesToMove
* - Update : @param oldNodes NodesToUpdate, @param newNodes NodesUpdated
*/
class ActionNodesValues(val oldNodes: List<Node>, val newNodes: List<Node>)
class ActionNodeValues(val result: ActionRunnable.Result, val oldNodes: List<NodeVersioned>, val newNodes: List<NodeVersioned>)
abstract class AfterActionNodesFinish {
abstract fun onActionNodesFinish(result: ActionRunnable.Result, actionNodesValues: ActionNodesValues)
abstract class AfterActionNodeFinishRunnable {
abstract fun onActionNodeFinish(actionNodeValues: ActionNodeValues)
}

View File

@@ -22,29 +22,30 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException
import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException
import com.kunzisoft.keepass.database.exception.CopyDatabaseEntryException
import com.kunzisoft.keepass.database.exception.CopyDatabaseGroupException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
class CopyNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToCopy: List<Node>,
private val mNewParent: Group,
private val mNodesToCopy: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterAddNodeRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mEntriesCopied = ArrayList<Entry>()
private var mEntriesCopied = ArrayList<EntryVersioned>()
override fun nodeAction() {
var error: LoadDatabaseException? = null
foreachNode@ for(currentNode in mNodesToCopy) {
when (currentNode.type) {
Type.GROUP -> {
Log.e(TAG, "Copy not allowed for group")// Only finish thread
setError(CopyGroupDatabaseException())
error = CopyDatabaseGroupException()
break@foreachNode
}
Type.ENTRY -> {
@@ -53,26 +54,30 @@ class CopyNodesRunnable constructor(
// Update entry with new values
mNewParent.touch(modified = false, touchParents = true)
val entryCopied = database.copyEntryTo(currentNode as Entry, mNewParent)
val entryCopied = database.copyEntryTo(currentNode as EntryVersioned, mNewParent)
if (entryCopied != null) {
entryCopied.touch(modified = true, touchParents = true)
mEntriesCopied.add(entryCopied)
} else {
Log.e(TAG, "Unable to create a copy of the entry")
setError(CopyEntryDatabaseException())
error = CopyDatabaseEntryException()
break@foreachNode
}
} else {
// Only finish thread
setError(CopyEntryDatabaseException())
error = CopyDatabaseEntryException()
break@foreachNode
}
}
}
}
if (error != null)
throwErrorAndFinish(error)
else
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, try to delete the copy
mEntriesCopied.forEach {
@@ -83,7 +88,7 @@ class CopyNodesRunnable constructor(
}
}
}
return ActionNodesValues(mNodesToCopy, mEntriesCopied)
return ActionNodeValues(result, mNodesToCopy, mEntriesCopied)
}
companion object {

View File

@@ -21,20 +21,18 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
class DeleteNodesRunnable(context: Context,
database: Database,
private val mNodesToDelete: List<Node>,
private val mNodesToDelete: List<NodeVersioned>,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
finish: AfterActionNodeFinishRunnable)
: ActionNodeDatabaseRunnable(context, database, finish, save) {
private var mParent: Group? = null
private var mParent: GroupVersioned? = null
private var mCanRecycle: Boolean = false
private var mNodesToDeleteBackup = ArrayList<Node>()
private var mNodesToDeleteBackup = ArrayList<NodeVersioned>()
override fun nodeAction() {
@@ -45,7 +43,7 @@ class DeleteNodesRunnable(context: Context,
when (currentNode.type) {
Type.GROUP -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Group(currentNode as Group))
mNodesToDeleteBackup.add(GroupVersioned(currentNode as GroupVersioned))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
@@ -56,7 +54,7 @@ class DeleteNodesRunnable(context: Context,
}
Type.ENTRY -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Entry(currentNode as Entry))
mNodesToDeleteBackup.add(EntryVersioned(currentNode as EntryVersioned))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
@@ -67,19 +65,20 @@ class DeleteNodesRunnable(context: Context,
}
}
}
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
if (mCanRecycle) {
mParent?.let {
mNodesToDeleteBackup.forEach { backupNode ->
when (backupNode.type) {
Type.GROUP -> {
database.undoRecycle(backupNode as Group, it)
database.undoRecycle(backupNode as GroupVersioned, it)
}
Type.ENTRY -> {
database.undoRecycle(backupNode as Entry, it)
database.undoRecycle(backupNode as EntryVersioned, it)
}
}
}
@@ -93,6 +92,6 @@ class DeleteNodesRunnable(context: Context,
// Return a copy of unchanged nodes as old param
// and nodes deleted or moved in recycle bin as new param
return ActionNodesValues(mNodesToDeleteBackup, mNodesToDelete)
return ActionNodeValues(result, mNodesToDeleteBackup, mNodesToDelete)
}
}

View File

@@ -22,31 +22,31 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.EntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.MoveDatabaseEntryException
import com.kunzisoft.keepass.database.exception.MoveDatabaseGroupException
class MoveNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToMove: List<Node>,
private val mNewParent: Group,
private val mNodesToMove: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterAddNodeRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mOldParent: Group? = null
private var mOldParent: GroupVersioned? = null
override fun nodeAction() {
var error: LoadDatabaseException? = null
foreachNode@ for(nodeToMove in mNodesToMove) {
// Move node in new parent
mOldParent = nodeToMove.parent
when (nodeToMove.type) {
Type.GROUP -> {
val groupToMove = nodeToMove as Group
val groupToMove = nodeToMove as GroupVersioned
// Move group in new parent if not in the current group
if (groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
@@ -54,12 +54,12 @@ class MoveNodesRunnable constructor(
database.moveGroupTo(groupToMove, mNewParent)
} else {
// Only finish thread
setError(MoveGroupDatabaseException())
error = MoveDatabaseGroupException()
break@foreachNode
}
}
Type.ENTRY -> {
val entryToMove = nodeToMove as Entry
val entryToMove = nodeToMove as EntryVersioned
// Move only if the parent change
if (mOldParent != mNewParent
// and root can contains entry
@@ -68,15 +68,19 @@ class MoveNodesRunnable constructor(
database.moveEntryTo(entryToMove, mNewParent)
} else {
// Only finish thread
setError(EntryDatabaseException())
error = MoveDatabaseEntryException()
break@foreachNode
}
}
}
}
if (error != null)
throwErrorAndFinish(error)
else
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
try {
mNodesToMove.forEach { nodeToMove ->
@@ -84,8 +88,8 @@ class MoveNodesRunnable constructor(
if (mOldParent != null &&
mOldParent != nodeToMove.parent) {
when (nodeToMove.type) {
Type.GROUP -> database.moveGroupTo(nodeToMove as Group, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as Entry, mOldParent!!)
Type.GROUP -> database.moveGroupTo(nodeToMove as GroupVersioned, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as EntryVersioned, mOldParent!!)
}
}
}
@@ -93,7 +97,7 @@ class MoveNodesRunnable constructor(
Log.i(TAG, "Unable to replace the node")
}
}
return ActionNodesValues(ArrayList(), mNodesToMove)
return ActionNodeValues(result, ArrayList(), mNodesToMove)
}
companion object {

View File

@@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class UpdateEntryRunnable constructor(
context: Context,
database: Database,
private val mOldEntry: Entry,
private val mNewEntry: Entry,
private val mOldEntry: EntryVersioned,
private val mNewEntry: EntryVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
finishRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, finishRunnable, save) {
// Keep backup of original values in case save fails
private var mBackupEntryHistory: Entry = Entry(mOldEntry)
private var mBackupEntryHistory: EntryVersioned = EntryVersioned(mOldEntry)
override fun nodeAction() {
// WARNING : Re attribute parent removed in entry edit activity to save memory
@@ -45,24 +45,26 @@ class UpdateEntryRunnable constructor(
mNewEntry.touch(modified = true, touchParents = true)
// Create an entry history (an entry history don't have history)
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
database.removeOldestEntryHistory(mOldEntry)
mOldEntry.addEntryToHistory(EntryVersioned(mBackupEntryHistory, copyHistory = false))
database.removeOldestHistory(mOldEntry)
// Only change data in index
database.updateEntry(mOldEntry)
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
mOldEntry.updateWith(mBackupEntryHistory)
// If we fail to save, back out changes to global structure
database.updateEntry(mOldEntry)
}
val oldNodesReturn = ArrayList<Node>()
val oldNodesReturn = ArrayList<NodeVersioned>()
oldNodesReturn.add(mBackupEntryHistory)
val newNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mOldEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
}
}

View File

@@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class UpdateGroupRunnable constructor(
context: Context,
database: Database,
private val mOldGroup: Group,
private val mNewGroup: Group,
private val mOldGroup: GroupVersioned,
private val mNewGroup: GroupVersioned,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
finishRunnable: AfterActionNodeFinishRunnable?)
: ActionNodeDatabaseRunnable(context, database, finishRunnable, save) {
// Keep backup of original values in case save fails
private val mBackupGroup: Group = Group(mOldGroup)
private val mBackupGroup: GroupVersioned = GroupVersioned(mOldGroup)
override fun nodeAction() {
// WARNING : Re attribute parent and children removed in group activity to save memory
@@ -47,19 +47,21 @@ class UpdateGroupRunnable constructor(
// Only change data in index
database.updateGroup(mOldGroup)
saveDatabaseAndFinish()
}
override fun nodeFinish(): ActionNodesValues {
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
mOldGroup.updateWith(mBackupGroup)
database.updateGroup(mOldGroup)
}
val oldNodesReturn = ArrayList<Node>()
val oldNodesReturn = ArrayList<NodeVersioned>()
oldNodesReturn.add(mBackupGroup)
val newNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mOldGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
}
}

View File

@@ -2,11 +2,11 @@ package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.PwEntry
import com.kunzisoft.keepass.database.element.PwIconFactory
import com.kunzisoft.keepass.database.element.PwNodeId
abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf(
abstract class EntryCursor<EntryId, PwEntryV : PwEntry<*, EntryId, *, *>> : MatrixCursor(arrayOf(
_ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
@@ -24,9 +24,9 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
abstract fun addEntry(entry: PwEntryV)
abstract fun getPwNodeId(): NodeId<EntryId>
abstract fun getPwNodeId(): PwNodeId<EntryId>
open fun populateEntry(pwEntry: PwEntryV, iconFactory: IconImageFactory) {
open fun populateEntry(pwEntry: PwEntryV, iconFactory: PwIconFactory) {
pwEntry.nodeId = getPwNodeId()
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))

View File

@@ -1,14 +1,14 @@
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.PwEntry
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.database.element.PwNodeIdUUID
import java.util.*
abstract class EntryCursorUUID<EntryV: EntryVersioned<*, UUID, *, *>>: EntryCursor<UUID, EntryV>() {
abstract class EntryCursorUUID<EntryV: PwEntry<*, UUID, *, *>>: EntryCursor<UUID, EntryV>() {
override fun getPwNodeId(): NodeId<UUID> {
return NodeIdUUID(
override fun getPwNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID(
UUID(getLong(getColumnIndex(COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))))
}

View File

@@ -1,19 +1,19 @@
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.PwDatabase
import com.kunzisoft.keepass.database.element.PwEntryV3
class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
class EntryCursorV3 : EntryCursorUUID<PwEntryV3>() {
override fun addEntry(entry: EntryKDB) {
override fun addEntry(entry: PwEntryV3) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
entry.id.leastSignificantBits,
entry.title,
entry.icon.iconId,
DatabaseVersioned.UUID_ZERO.mostSignificantBits,
DatabaseVersioned.UUID_ZERO.leastSignificantBits,
PwDatabase.UUID_ZERO.mostSignificantBits,
PwDatabase.UUID_ZERO.leastSignificantBits,
entry.username,
entry.password,
entry.url,

View File

@@ -1,15 +1,15 @@
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.element.PwIconFactory
import java.util.UUID
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
class EntryCursorV4 : EntryCursorUUID<PwEntryV4>() {
private val extraFieldCursor: ExtraFieldCursor = ExtraFieldCursor()
override fun addEntry(entry: EntryKDBX) {
override fun addEntry(entry: PwEntryV4) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
@@ -31,7 +31,7 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entryId++
}
override fun populateEntry(pwEntry: EntryKDBX, iconFactory: IconImageFactory) {
override fun populateEntry(pwEntry: PwEntryV4, iconFactory: PwIconFactory) {
super.populateEntry(pwEntry, iconFactory)
// Retrieve custom icon

View File

@@ -3,7 +3,7 @@ package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.element.security.ProtectedString
class ExtraFieldCursor : MatrixCursor(arrayOf(
@@ -22,8 +22,8 @@ class ExtraFieldCursor : MatrixCursor(arrayOf(
fieldId++
}
fun populateExtraFieldInEntry(pwEntry: EntryKDBX) {
pwEntry.putExtraField(getString(getColumnIndex(COLUMN_LABEL)),
fun populateExtraFieldInEntry(pwEntry: PwEntryV4) {
pwEntry.addExtraField(getString(getColumnIndex(COLUMN_LABEL)),
ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0,
getString(getColumnIndex(COLUMN_VALUE))))
}

View File

@@ -17,12 +17,12 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.MemoryUtil
import java.util.HashMap
@@ -48,7 +48,7 @@ class AutoType : Parcelable {
this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = parcel.readLong()
this.defaultSequence = parcel.readString() ?: defaultSequence
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
this.windowSeqPairs = MemoryUtil.readStringParcelableMap(parcel)
}
override fun describeContents(): Int {
@@ -59,7 +59,7 @@ class AutoType : Parcelable {
dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeLong(obfuscationOptions)
dest.writeString(defaultSequence)
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
MemoryUtil.writeStringParcelableMap(dest, windowSeqPairs)
}
fun put(key: String, value: String) {

View File

@@ -17,30 +17,28 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element
import android.util.SparseArray
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.io.IOException
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
class BinaryPool {
private val pool = SparseArray<BinaryAttachment>()
private val pool = SparseArray<ProtectedBinary>()
operator fun get(key: Int): BinaryAttachment? {
operator fun get(key: Int): ProtectedBinary? {
return pool[key]
}
fun put(key: Int, value: BinaryAttachment) {
fun put(key: Int, value: ProtectedBinary) {
pool.put(key, value)
}
fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) {
fun doForEachBinary(action: (key: Int, binary: ProtectedBinary) -> Unit) {
for (i in 0 until pool.size()) {
action.invoke(i, pool.get(pool.keyAt(i)))
}
}
@Throws(IOException::class)
fun clear() {
doForEachBinary { _, binary ->
binary.clear()
@@ -48,10 +46,9 @@ class BinaryPool {
pool.clear()
}
fun add(fileBinary: BinaryAttachment) {
if (findKey(fileBinary) == null) {
pool.put(findUnusedKey(), fileBinary)
}
fun add(protectedBinary: ProtectedBinary) {
if (findKey(protectedBinary) != -1) return
pool.put(findUnusedKey(), protectedBinary)
}
fun findUnusedKey(): Int {
@@ -61,10 +58,10 @@ class BinaryPool {
return unusedKey
}
fun findKey(pb: BinaryAttachment): Int? {
fun findKey(pb: ProtectedBinary): Int {
for (i in 0 until pool.size()) {
if (pool.get(pool.keyAt(i)) == pb) return i
}
return null
return -1
}
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import java.util.*
class DateInstant : Parcelable {
private var jDate: Date = Date()
val date: Date
get() = jDate
constructor(source: DateInstant) {
this.jDate = Date(source.jDate.time)
}
constructor(date: Date) {
jDate = Date(date.time)
}
constructor(millis: Long) {
jDate = Date(millis)
}
constructor() {
jDate = Date()
}
protected constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as Date
}
override fun describeContents(): Int {
return 0
}
fun getDateTimeString(resources: Resources): String {
return Companion.getDateTimeString(resources, this.date)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(date)
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null) {
return false
}
if (javaClass != other.javaClass) {
return false
}
val date = other as DateInstant
return isSameDate(jDate, date.jDate)
}
override fun hashCode(): Int {
return jDate.hashCode()
}
override fun toString(): String {
return jDate.toString()
}
companion object {
val NEVER_EXPIRE = neverExpire
private val neverExpire: DateInstant
get() {
val 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 DateInstant(cal.time)
}
@JvmField
val CREATOR: Parcelable.Creator<DateInstant> = object : Parcelable.Creator<DateInstant> {
override fun createFromParcel(parcel: Parcel): DateInstant {
return DateInstant(parcel)
}
override fun newArray(size: Int): Array<DateInstant?> {
return arrayOfNulls(size)
}
}
private fun isSameDate(d1: Date?, d2: Date?): Boolean {
val cal1 = Calendar.getInstance()
cal1.time = d1
cal1.set(Calendar.MILLISECOND, 0)
val cal2 = Calendar.getInstance()
cal2.time = d2
cal2.set(Calendar.MILLISECOND, 0)
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) &&
cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) &&
cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
}
fun getDateTimeString(resources: Resources, date: Date): String {
return java.text.DateFormat.getDateTimeInstance(
java.text.DateFormat.MEDIUM,
java.text.DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(date)
}
}
}

View File

@@ -1,406 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.ArrayList
class Entry : Node, EntryVersionedInterface<Group> {
var entryKDB: EntryKDB? = null
private set
var entryKDBX: EntryKDBX? = null
private set
fun updateWith(entry: Entry, copyHistory: Boolean = true) {
entry.entryKDB?.let {
this.entryKDB?.updateWith(it)
}
entry.entryKDBX?.let {
this.entryKDBX?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: Entry, copyHistory: Boolean = true) {
if (entry.entryKDB != null) {
this.entryKDB = EntryKDB()
}
if (entry.entryKDBX != null) {
this.entryKDBX = EntryKDBX()
}
updateWith(entry, copyHistory)
}
constructor(entry: EntryKDB) {
this.entryKDBX = null
this.entryKDB = entry
}
constructor(entry: EntryKDBX) {
this.entryKDB = null
this.entryKDBX = entry
}
constructor(parcel: Parcel) {
entryKDB = parcel.readParcelable(EntryKDB::class.java.classLoader)
entryKDBX = parcel.readParcelable(EntryKDBX::class.java.classLoader)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(entryKDB, flags)
dest.writeParcelable(entryKDBX, flags)
}
override var nodeId: NodeId<UUID>
get() = entryKDBX?.nodeId ?: entryKDB?.nodeId ?: NodeIdUUID()
set(value) {
entryKDB?.nodeId = value
entryKDBX?.nodeId = value
}
override var title: String
get() = entryKDB?.title ?: entryKDBX?.title ?: ""
set(value) {
entryKDB?.title = value
entryKDBX?.title = value
}
override var icon: IconImage
get() {
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImageStandard()
}
set(value) {
entryKDB?.icon = value
entryKDBX?.icon = value
}
override val type: Type
get() = Type.ENTRY
override var parent: Group?
get() {
entryKDB?.parent?.let {
return Group(it)
}
entryKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
entryKDB?.parent = value?.groupKDB
entryKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return entryKDB?.containsParent() ?: entryKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
entryKDBX?.afterChangeParent()
}
override fun touch(modified: Boolean, touchParents: Boolean) {
entryKDB?.touch(modified, touchParents)
entryKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = false
container.groupKDB?.let {
contained = entryKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = entryKDBX?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: DateInstant
get() = entryKDB?.creationTime ?: entryKDBX?.creationTime ?: DateInstant()
set(value) {
entryKDB?.creationTime = value
entryKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = entryKDB?.lastModificationTime ?: entryKDBX?.lastModificationTime ?: DateInstant()
set(value) {
entryKDB?.lastModificationTime = value
entryKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = entryKDB?.lastAccessTime ?: entryKDBX?.lastAccessTime ?: DateInstant()
set(value) {
entryKDB?.lastAccessTime = value
entryKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = entryKDB?.expiryTime ?: entryKDBX?.expiryTime ?: DateInstant()
set(value) {
entryKDB?.expiryTime = value
entryKDBX?.expiryTime = value
}
override var expires: Boolean
get() = entryKDB?.expires ?: entryKDBX?.expires ?: false
set(value) {
entryKDB?.expires = value
entryKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = entryKDB?.isCurrentlyExpires ?: entryKDBX?.isCurrentlyExpires ?: false
override var username: String
get() = entryKDB?.username ?: entryKDBX?.username ?: ""
set(value) {
entryKDB?.username = value
entryKDBX?.username = value
}
override var password: String
get() = entryKDB?.password ?: entryKDBX?.password ?: ""
set(value) {
entryKDB?.password = value
entryKDBX?.password = value
}
override var url: String
get() = entryKDB?.url ?: entryKDBX?.url ?: ""
set(value) {
entryKDB?.url = value
entryKDBX?.url = value
}
override var notes: String
get() = entryKDB?.notes ?: entryKDBX?.notes ?: ""
set(value) {
entryKDB?.notes = value
entryKDBX?.notes = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
fun getVisualTitle(): String {
return getVisualTitle(isTan(),
title,
username,
url,
nodeId.toString())
}
/*
------------
KDB Methods
------------
*/
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
val isMetaStream: Boolean
get() = entryKDB?.isMetaStream ?: false
/*
------------
KDBX Methods
------------
*/
var iconCustom: IconImageCustom
get() = entryKDBX?.iconCustom ?: IconImageCustom.UNKNOWN_ICON
set(value) {
entryKDBX?.iconCustom = value
}
/**
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value
*/
val customFields: HashMap<String, ProtectedString>
get() = entryKDBX?.customFields ?: HashMap()
/**
* To redefine if version of entry allow custom field,
* @return true if entry allows custom field
*/
fun allowCustomFields(): Boolean {
return entryKDBX?.allowCustomFields() ?: false
}
fun removeAllFields() {
entryKDBX?.removeAllFields()
}
/**
* Update or add an extra field to the list (standard or custom)
* @param label Label of field, must be unique
* @param value Value of field
*/
fun putExtraField(label: String, value: ProtectedString) {
entryKDBX?.putExtraField(label, value)
}
fun getOtpElement(): OtpElement? {
return OtpEntryFields.parseFields { key ->
customFields[key]?.toString()
}
}
fun startToManageFieldReferences(db: DatabaseKDBX) {
entryKDBX?.startToManageFieldReferences(db)
}
fun stopToManageFieldReferences() {
entryKDBX?.stopToManageFieldReferences()
}
fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>()
val entryV4History = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryV4History) {
history.add(Entry(entryHistory))
}
return history
}
fun addEntryToHistory(entry: Entry) {
entry.entryKDBX?.let {
entryKDBX?.addEntryToHistory(it)
}
}
fun removeAllHistory() {
entryKDBX?.removeAllHistory()
}
fun removeOldestEntryFromHistory() {
entryKDBX?.removeOldestEntryFromHistory()
}
fun getSize(): Long {
return entryKDBX?.size ?: 0L
}
fun containsCustomData(): Boolean {
return entryKDBX?.containsCustomData() ?: false
}
/*
------------
Converter
------------
*/
/**
* Retrieve generated entry info,
* Remove parameter fields and add auto generated elements in auto custom fields
*/
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
val entryInfo = EntryInfo()
if (raw)
database?.stopManageEntry(this)
else
database?.startManageEntry(this)
entryInfo.id = nodeId.toString()
entryInfo.title = title
entryInfo.username = username
entryInfo.password = password
entryInfo.url = url
entryInfo.notes = notes
for (entry in customFields.entries) {
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
if (!raw)
database?.stopManageEntry(this)
return entryInfo
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Entry
if (entryKDB != other.entryKDB) return false
if (entryKDBX != other.entryKDBX) return false
return true
}
override fun hashCode(): Int {
var result = entryKDB?.hashCode() ?: 0
result = 31 * result + (entryKDBX?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>"
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(isTan: Boolean, title: String, userName: String, url: String, id: String): String {
return if (isTan) {
"$PMS_TAN_ENTRY $userName"
} else {
if (title.isEmpty())
if (userName.isEmpty())
if (url.isEmpty())
id
else
url
else
userName
else
title
}
}
}
}

View File

@@ -0,0 +1,379 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import java.util.*
import kotlin.collections.ArrayList
class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
var pwEntryV3: PwEntryV3? = null
private set
var pwEntryV4: PwEntryV4? = null
private set
fun updateWith(entry: EntryVersioned, copyHistory: Boolean = true) {
entry.pwEntryV3?.let {
this.pwEntryV3?.updateWith(it)
}
entry.pwEntryV4?.let {
this.pwEntryV4?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: EntryVersioned, copyHistory: Boolean = true) {
if (entry.pwEntryV3 != null) {
this.pwEntryV3 = PwEntryV3()
}
if (entry.pwEntryV4 != null) {
this.pwEntryV4 = PwEntryV4()
}
updateWith(entry, copyHistory)
}
constructor(entry: PwEntryV3) {
this.pwEntryV4 = null
this.pwEntryV3 = entry
}
constructor(entry: PwEntryV4) {
this.pwEntryV3 = null
this.pwEntryV4 = entry
}
constructor(parcel: Parcel) {
pwEntryV3 = parcel.readParcelable(PwEntryV3::class.java.classLoader)
pwEntryV4 = parcel.readParcelable(PwEntryV4::class.java.classLoader)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(pwEntryV3, flags)
dest.writeParcelable(pwEntryV4, flags)
}
override var nodeId: PwNodeId<UUID>
get() = pwEntryV4?.nodeId ?: pwEntryV3?.nodeId ?: PwNodeIdUUID()
set(value) {
pwEntryV3?.nodeId = value
pwEntryV4?.nodeId = value
}
override var title: String
get() = pwEntryV3?.title ?: pwEntryV4?.title ?: ""
set(value) {
pwEntryV3?.title = value
pwEntryV4?.title = value
}
override var icon: PwIcon
get() {
return pwEntryV3?.icon ?: pwEntryV4?.icon ?: PwIconStandard()
}
set(value) {
pwEntryV3?.icon = value
pwEntryV4?.icon = value
}
override val type: Type
get() = Type.ENTRY
override var parent: GroupVersioned?
get() {
pwEntryV3?.parent?.let {
return GroupVersioned(it)
}
pwEntryV4?.parent?.let {
return GroupVersioned(it)
}
return null
}
set(value) {
pwEntryV3?.parent = value?.pwGroupV3
pwEntryV4?.parent = value?.pwGroupV4
}
override fun containsParent(): Boolean {
return pwEntryV3?.containsParent() ?: pwEntryV4?.containsParent() ?: false
}
override fun afterAssignNewParent() {
pwEntryV4?.afterChangeParent()
}
override fun touch(modified: Boolean, touchParents: Boolean) {
pwEntryV3?.touch(modified, touchParents)
pwEntryV4?.touch(modified, touchParents)
}
override fun isContainedIn(container: GroupVersioned): Boolean {
var contained: Boolean? = false
container.pwGroupV3?.let {
contained = pwEntryV3?.isContainedIn(it)
}
container.pwGroupV4?.let {
contained = pwEntryV4?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: PwDate
get() = pwEntryV3?.creationTime ?: pwEntryV4?.creationTime ?: PwDate()
set(value) {
pwEntryV3?.creationTime = value
pwEntryV4?.creationTime = value
}
override var lastModificationTime: PwDate
get() = pwEntryV3?.lastModificationTime ?: pwEntryV4?.lastModificationTime ?: PwDate()
set(value) {
pwEntryV3?.lastModificationTime = value
pwEntryV4?.lastModificationTime = value
}
override var lastAccessTime: PwDate
get() = pwEntryV3?.lastAccessTime ?: pwEntryV4?.lastAccessTime ?: PwDate()
set(value) {
pwEntryV3?.lastAccessTime = value
pwEntryV4?.lastAccessTime = value
}
override var expiryTime: PwDate
get() = pwEntryV3?.expiryTime ?: pwEntryV4?.expiryTime ?: PwDate()
set(value) {
pwEntryV3?.expiryTime = value
pwEntryV4?.expiryTime = value
}
override var expires: Boolean
get() = pwEntryV3?.expires ?: pwEntryV4?.expires ?: false
set(value) {
pwEntryV3?.expires = value
pwEntryV4?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = pwEntryV3?.isCurrentlyExpires ?: pwEntryV4?.isCurrentlyExpires ?: false
override var username: String
get() = pwEntryV3?.username ?: pwEntryV4?.username ?: ""
set(value) {
pwEntryV3?.username = value
pwEntryV4?.username = value
}
override var password: String
get() = pwEntryV3?.password ?: pwEntryV4?.password ?: ""
set(value) {
pwEntryV3?.password = value
pwEntryV4?.password = value
}
override var url: String
get() = pwEntryV3?.url ?: pwEntryV4?.url ?: ""
set(value) {
pwEntryV3?.url = value
pwEntryV4?.url = value
}
override var notes: String
get() = pwEntryV3?.notes ?: pwEntryV4?.notes ?: ""
set(value) {
pwEntryV3?.notes = value
pwEntryV4?.notes = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
fun getVisualTitle(): String {
return getVisualTitle(isTan(),
title,
username,
url,
nodeId.toString())
}
/*
------------
V3 Methods
------------
*/
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
val isMetaStream: Boolean
get() = pwEntryV3?.isMetaStream ?: false
/*
------------
V4 Methods
------------
*/
var iconCustom: PwIconCustom
get() = pwEntryV4?.iconCustom ?: PwIconCustom.UNKNOWN_ICON
set(value) {
pwEntryV4?.iconCustom = value
}
/**
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value
*/
val customFields: HashMap<String, ProtectedString>
get() = pwEntryV4?.customFields ?: HashMap()
/**
* To redefine if version of entry allow custom field,
* @return true if entry allows custom field
*/
fun allowCustomFields(): Boolean {
return pwEntryV4?.allowCustomFields() ?: false
}
fun removeAllFields() {
pwEntryV4?.removeAllFields()
}
/**
* Add an extra field to the list (standard or custom)
* @param label Label of field, must be unique
* @param value Value of field
*/
fun addExtraField(label: String, value: ProtectedString) {
pwEntryV4?.addExtraField(label, value)
}
fun startToManageFieldReferences(db: PwDatabaseV4) {
pwEntryV4?.startToManageFieldReferences(db)
}
fun stopToManageFieldReferences() {
pwEntryV4?.stopToManageFieldReferences()
}
fun getHistory(): ArrayList<EntryVersioned> {
val history = ArrayList<EntryVersioned>()
val entryV4History = pwEntryV4?.history ?: ArrayList()
for (entryHistory in entryV4History) {
history.add(EntryVersioned(entryHistory))
}
return history
}
fun addEntryToHistory(entry: EntryVersioned) {
entry.pwEntryV4?.let {
pwEntryV4?.addEntryToHistory(it)
}
}
fun removeAllHistory() {
pwEntryV4?.removeAllHistory()
}
fun removeOldestEntryFromHistory() {
pwEntryV4?.removeOldestEntryFromHistory()
}
fun getSize(): Long {
return pwEntryV4?.size ?: 0L
}
fun containsCustomData(): Boolean {
return pwEntryV4?.containsCustomData() ?: false
}
/*
------------
Converter
------------
*/
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
val entryInfo = EntryInfo()
if (raw)
database?.stopManageEntry(this)
else
database?.startManageEntry(this)
entryInfo.id = nodeId.toString()
entryInfo.title = title
entryInfo.username = username
entryInfo.password = password
entryInfo.url = url
entryInfo.notes = notes
for (entry in customFields.entries) {
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
if (!raw)
database?.stopManageEntry(this)
return entryInfo
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as EntryVersioned
if (pwEntryV3 != other.pwEntryV3) return false
if (pwEntryV4 != other.pwEntryV4) return false
return true
}
override fun hashCode(): Int {
var result = pwEntryV3?.hashCode() ?: 0
result = 31 * result + (pwEntryV4?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<EntryVersioned> {
override fun createFromParcel(parcel: Parcel): EntryVersioned {
return EntryVersioned(parcel)
}
override fun newArray(size: Int): Array<EntryVersioned?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>"
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(isTan: Boolean, title: String, userName: String, url: String, id: String): String {
return if (isTan) {
"$PMS_TAN_ENTRY $userName"
} else {
if (title.isEmpty())
if (userName.isEmpty())
if (url.isEmpty())
id
else
url
else
userName
else
title
}
}
}
}

View File

@@ -1,358 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.*
import java.util.*
import kotlin.collections.ArrayList
class Group : Node, GroupVersionedInterface<Group, Entry> {
var groupKDB: GroupKDB? = null
private set
var groupKDBX: GroupKDBX? = null
private set
fun updateWith(group: Group) {
group.groupKDB?.let {
this.groupKDB?.updateWith(it)
}
group.groupKDBX?.let {
this.groupKDBX?.updateWith(it)
}
}
/**
* Use this constructor to copy a Group
*/
constructor(group: Group) {
if (group.groupKDB != null) {
if (this.groupKDB == null)
this.groupKDB = GroupKDB()
}
if (group.groupKDBX != null) {
if (this.groupKDBX == null)
this.groupKDBX = GroupKDBX()
}
updateWith(group)
}
constructor(group: GroupKDB) {
this.groupKDBX = null
this.groupKDB = group
}
constructor(group: GroupKDBX) {
this.groupKDB = null
this.groupKDBX = group
}
constructor(parcel: Parcel) {
groupKDB = parcel.readParcelable(GroupKDB::class.java.classLoader)
groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
companion object CREATOR : Parcelable.Creator<Group> {
override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel)
}
override fun newArray(size: Int): Array<Group?> {
return arrayOfNulls(size)
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(groupKDB, flags)
dest.writeParcelable(groupKDBX, flags)
}
override val nodeId: NodeId<*>?
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId
override var title: String
get() = groupKDB?.title ?: groupKDBX?.title ?: ""
set(value) {
groupKDB?.title = value
groupKDBX?.title = value
}
override var icon: IconImage
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImageStandard()
set(value) {
groupKDB?.icon = value
groupKDBX?.icon = value
}
override val type: Type
get() = Type.GROUP
override var parent: Group?
get() {
groupKDB?.parent?.let {
return Group(it)
}
groupKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
groupKDB?.parent = value?.groupKDB
groupKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return groupKDB?.containsParent() ?: groupKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
groupKDB?.afterAssignNewParent()
groupKDBX?.afterAssignNewParent()
}
fun addChildrenFrom(group: Group) {
group.groupKDB?.getChildEntries()?.forEach { entryToAdd ->
groupKDB?.addChildEntry(entryToAdd)
}
group.groupKDB?.getChildGroups()?.forEach { groupToAdd ->
groupKDB?.addChildGroup(groupToAdd)
}
group.groupKDBX?.getChildEntries()?.forEach { entryToAdd ->
groupKDBX?.addChildEntry(entryToAdd)
}
group.groupKDBX?.getChildGroups()?.forEach { groupToAdd ->
groupKDBX?.addChildGroup(groupToAdd)
}
}
override fun touch(modified: Boolean, touchParents: Boolean) {
groupKDB?.touch(modified, touchParents)
groupKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = null
container.groupKDB?.let {
contained = groupKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = groupKDBX?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: DateInstant
get() = groupKDB?.creationTime ?: groupKDBX?.creationTime ?: DateInstant()
set(value) {
groupKDB?.creationTime = value
groupKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = groupKDB?.lastModificationTime ?: groupKDBX?.lastModificationTime ?: DateInstant()
set(value) {
groupKDB?.lastModificationTime = value
groupKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = groupKDB?.lastAccessTime ?: groupKDBX?.lastAccessTime ?: DateInstant()
set(value) {
groupKDB?.lastAccessTime = value
groupKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = groupKDB?.expiryTime ?: groupKDBX?.expiryTime ?: DateInstant()
set(value) {
groupKDB?.expiryTime = value
groupKDBX?.expiryTime = value
}
override var expires: Boolean
get() = groupKDB?.expires ?: groupKDBX?.expires ?: false
set(value) {
groupKDB?.expires = value
groupKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false
override fun getChildGroups(): MutableList<Group> {
val children = ArrayList<Group>()
groupKDB?.getChildGroups()?.forEach {
children.add(Group(it))
}
groupKDBX?.getChildGroups()?.forEach {
children.add(Group(it))
}
return children
}
override fun getChildEntries(): MutableList<Entry> {
return getChildEntries(false)
}
fun getChildEntries(withoutMetaStream: Boolean): MutableList<Entry> {
val children = ArrayList<Entry>()
groupKDB?.getChildEntries()?.forEach {
val entryToAddAsChild = Entry(it)
if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
children.add(entryToAddAsChild)
}
groupKDBX?.getChildEntries()?.forEach {
children.add(Entry(it))
}
return children
}
/**
* Filter MetaStream entries and return children
* @return List of direct children (one level below) as NodeVersioned
*/
fun getChildren(withoutMetaStream: Boolean = true): List<Node> {
val children = ArrayList<Node>()
children.addAll(getChildGroups())
groupKDB?.let {
children.addAll(getChildEntries(withoutMetaStream))
}
groupKDBX?.let {
// No MetasStream in V4
children.addAll(getChildEntries(withoutMetaStream))
}
return children
}
override fun addChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.addChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.addChildGroup(it)
}
}
override fun addChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.addChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.addChildEntry(it)
}
}
override fun removeChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.removeChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.removeChildGroup(it)
}
}
override fun removeChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.removeChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.removeChildEntry(it)
}
}
override fun removeChildren() {
groupKDB?.removeChildren()
groupKDBX?.removeChildren()
}
override fun allowAddEntryIfIsRoot(): Boolean {
return groupKDB?.allowAddEntryIfIsRoot() ?: groupKDBX?.allowAddEntryIfIsRoot() ?: false
}
/*
------------
KDB Methods
------------
*/
var nodeIdKDB: NodeId<Int>
get() = groupKDB?.nodeId ?: NodeIdInt()
set(value) { groupKDB?.nodeId = value }
fun setNodeId(id: NodeIdInt) {
groupKDB?.nodeId = id
}
fun getLevel(): Int {
return groupKDB?.level ?: -1
}
fun setLevel(level: Int) {
groupKDB?.level = level
}
/*
------------
KDBX Methods
------------
*/
var nodeIdKDBX: NodeId<UUID>
get() = groupKDBX?.nodeId ?: NodeIdUUID()
set(value) { groupKDBX?.nodeId = value }
fun setNodeId(id: NodeIdUUID) {
groupKDBX?.nodeId = id
}
fun setEnableAutoType(enableAutoType: Boolean?) {
groupKDBX?.enableAutoType = enableAutoType
}
fun setEnableSearching(enableSearching: Boolean?) {
groupKDBX?.enableSearching = enableSearching
}
fun setExpanded(expanded: Boolean) {
groupKDBX?.isExpanded = expanded
}
fun containsCustomData(): Boolean {
return groupKDBX?.containsCustomData() ?: false
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Group
if (groupKDB != other.groupKDB) return false
if (groupKDBX != other.groupKDBX) return false
return true
}
override fun hashCode(): Int {
var result = groupKDB?.hashCode() ?: 0
result = 31 * result + (groupKDBX?.hashCode() ?: 0)
return result
}
}

View File

@@ -0,0 +1,363 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import java.util.*
import kotlin.collections.ArrayList
class GroupVersioned : NodeVersioned, PwGroupInterface<GroupVersioned, EntryVersioned> {
var pwGroupV3: PwGroupV3? = null
private set
var pwGroupV4: PwGroupV4? = null
private set
fun updateWith(group: GroupVersioned) {
group.pwGroupV3?.let {
this.pwGroupV3?.updateWith(it)
}
group.pwGroupV4?.let {
this.pwGroupV4?.updateWith(it)
}
}
/**
* Use this constructor to copy a Group
*/
constructor(group: GroupVersioned) {
if (group.pwGroupV3 != null) {
if (this.pwGroupV3 == null)
this.pwGroupV3 = PwGroupV3()
}
if (group.pwGroupV4 != null) {
if (this.pwGroupV4 == null)
this.pwGroupV4 = PwGroupV4()
}
updateWith(group)
}
constructor(group: PwGroupV3) {
this.pwGroupV4 = null
this.pwGroupV3 = group
}
constructor(group: PwGroupV4) {
this.pwGroupV3 = null
this.pwGroupV4 = group
}
constructor(parcel: Parcel) {
pwGroupV3 = parcel.readParcelable(PwGroupV3::class.java.classLoader)
pwGroupV4 = parcel.readParcelable(PwGroupV4::class.java.classLoader)
}
companion object CREATOR : Parcelable.Creator<GroupVersioned> {
override fun createFromParcel(parcel: Parcel): GroupVersioned {
return GroupVersioned(parcel)
}
override fun newArray(size: Int): Array<GroupVersioned?> {
return arrayOfNulls(size)
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(pwGroupV3, flags)
dest.writeParcelable(pwGroupV4, flags)
}
override val nodeId: PwNodeId<*>?
get() = pwGroupV4?.nodeId ?: pwGroupV3?.nodeId
override var title: String
get() = pwGroupV3?.title ?: pwGroupV4?.title ?: ""
set(value) {
pwGroupV3?.title = value
pwGroupV4?.title = value
}
override var icon: PwIcon
get() = pwGroupV3?.icon ?: pwGroupV4?.icon ?: PwIconStandard()
set(value) {
pwGroupV3?.icon = value
pwGroupV4?.icon = value
}
override val type: Type
get() = Type.GROUP
override var parent: GroupVersioned?
get() {
pwGroupV3?.parent?.let {
return GroupVersioned(it)
}
pwGroupV4?.parent?.let {
return GroupVersioned(it)
}
return null
}
set(value) {
pwGroupV3?.parent = value?.pwGroupV3
pwGroupV4?.parent = value?.pwGroupV4
}
override fun containsParent(): Boolean {
return pwGroupV3?.containsParent() ?: pwGroupV4?.containsParent() ?: false
}
override fun afterAssignNewParent() {
pwGroupV3?.afterAssignNewParent()
pwGroupV4?.afterAssignNewParent()
}
fun addChildrenFrom(group: GroupVersioned) {
group.pwGroupV3?.getChildEntries()?.forEach { entryToAdd ->
pwGroupV3?.addChildEntry(entryToAdd)
}
group.pwGroupV3?.getChildGroups()?.forEach { groupToAdd ->
pwGroupV3?.addChildGroup(groupToAdd)
}
group.pwGroupV4?.getChildEntries()?.forEach { entryToAdd ->
pwGroupV4?.addChildEntry(entryToAdd)
}
group.pwGroupV4?.getChildGroups()?.forEach { groupToAdd ->
pwGroupV4?.addChildGroup(groupToAdd)
}
}
fun removeChildren() {
pwGroupV3?.getChildEntries()?.forEach { entryToRemove ->
pwGroupV3?.removeChildEntry(entryToRemove)
}
pwGroupV3?.getChildGroups()?.forEach { groupToRemove ->
pwGroupV3?.removeChildGroup(groupToRemove)
}
pwGroupV4?.getChildEntries()?.forEach { entryToRemove ->
pwGroupV4?.removeChildEntry(entryToRemove)
}
pwGroupV4?.getChildGroups()?.forEach { groupToRemove ->
pwGroupV4?.removeChildGroup(groupToRemove)
}
}
override fun touch(modified: Boolean, touchParents: Boolean) {
pwGroupV3?.touch(modified, touchParents)
pwGroupV4?.touch(modified, touchParents)
}
override fun isContainedIn(container: GroupVersioned): Boolean {
var contained: Boolean? = null
container.pwGroupV3?.let {
contained = pwGroupV3?.isContainedIn(it)
}
container.pwGroupV4?.let {
contained = pwGroupV4?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: PwDate
get() = pwGroupV3?.creationTime ?: pwGroupV4?.creationTime ?: PwDate()
set(value) {
pwGroupV3?.creationTime = value
pwGroupV4?.creationTime = value
}
override var lastModificationTime: PwDate
get() = pwGroupV3?.lastModificationTime ?: pwGroupV4?.lastModificationTime ?: PwDate()
set(value) {
pwGroupV3?.lastModificationTime = value
pwGroupV4?.lastModificationTime = value
}
override var lastAccessTime: PwDate
get() = pwGroupV3?.lastAccessTime ?: pwGroupV4?.lastAccessTime ?: PwDate()
set(value) {
pwGroupV3?.lastAccessTime = value
pwGroupV4?.lastAccessTime = value
}
override var expiryTime: PwDate
get() = pwGroupV3?.expiryTime ?: pwGroupV4?.expiryTime ?: PwDate()
set(value) {
pwGroupV3?.expiryTime = value
pwGroupV4?.expiryTime = value
}
override var expires: Boolean
get() = pwGroupV3?.expires ?: pwGroupV4?.expires ?: false
set(value) {
pwGroupV3?.expires = value
pwGroupV4?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = pwGroupV3?.isCurrentlyExpires ?: pwGroupV4?.isCurrentlyExpires ?: false
override fun getChildGroups(): MutableList<GroupVersioned> {
val children = ArrayList<GroupVersioned>()
pwGroupV3?.getChildGroups()?.forEach {
children.add(GroupVersioned(it))
}
pwGroupV4?.getChildGroups()?.forEach {
children.add(GroupVersioned(it))
}
return children
}
override fun getChildEntries(): MutableList<EntryVersioned> {
return getChildEntries(false)
}
fun getChildEntries(withoutMetaStream: Boolean): MutableList<EntryVersioned> {
val children = ArrayList<EntryVersioned>()
pwGroupV3?.getChildEntries()?.forEach {
val entryToAddAsChild = EntryVersioned(it)
if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
children.add(entryToAddAsChild)
}
pwGroupV4?.getChildEntries()?.forEach {
children.add(EntryVersioned(it))
}
return children
}
/**
* Filter MetaStream entries and return children
* @return List of direct children (one level below) as PwNode
*/
fun getChildren(withoutMetaStream: Boolean = true): List<NodeVersioned> {
val children = ArrayList<NodeVersioned>()
children.addAll(getChildGroups())
pwGroupV3?.let {
children.addAll(getChildEntries(withoutMetaStream))
}
pwGroupV4?.let {
// No MetasStream in V4
children.addAll(getChildEntries(withoutMetaStream))
}
return children
}
override fun addChildGroup(group: GroupVersioned) {
group.pwGroupV3?.let {
pwGroupV3?.addChildGroup(it)
}
group.pwGroupV4?.let {
pwGroupV4?.addChildGroup(it)
}
}
override fun addChildEntry(entry: EntryVersioned) {
entry.pwEntryV3?.let {
pwGroupV3?.addChildEntry(it)
}
entry.pwEntryV4?.let {
pwGroupV4?.addChildEntry(it)
}
}
override fun removeChildGroup(group: GroupVersioned) {
group.pwGroupV3?.let {
pwGroupV3?.removeChildGroup(it)
}
group.pwGroupV4?.let {
pwGroupV4?.removeChildGroup(it)
}
}
override fun removeChildEntry(entry: EntryVersioned) {
entry.pwEntryV3?.let {
pwGroupV3?.removeChildEntry(it)
}
entry.pwEntryV4?.let {
pwGroupV4?.removeChildEntry(it)
}
}
override fun allowAddEntryIfIsRoot(): Boolean {
return pwGroupV3?.allowAddEntryIfIsRoot() ?: pwGroupV4?.allowAddEntryIfIsRoot() ?: false
}
/*
------------
V3 Methods
------------
*/
var nodeIdV3: PwNodeId<Int>
get() = pwGroupV3?.nodeId ?: PwNodeIdInt()
set(value) { pwGroupV3?.nodeId = value }
fun setNodeId(id: PwNodeIdInt) {
pwGroupV3?.nodeId = id
}
fun getLevel(): Int {
return pwGroupV3?.level ?: -1
}
fun setLevel(level: Int) {
pwGroupV3?.level = level
}
/*
------------
V4 Methods
------------
*/
var nodeIdV4: PwNodeId<UUID>
get() = pwGroupV4?.nodeId ?: PwNodeIdUUID()
set(value) { pwGroupV4?.nodeId = value }
fun setNodeId(id: PwNodeIdUUID) {
pwGroupV4?.nodeId = id
}
fun setEnableAutoType(enableAutoType: Boolean?) {
pwGroupV4?.enableAutoType = enableAutoType
}
fun setEnableSearching(enableSearching: Boolean?) {
pwGroupV4?.enableSearching = enableSearching
}
fun setExpanded(expanded: Boolean) {
pwGroupV4?.isExpanded = expanded
}
fun containsCustomData(): Boolean {
return pwGroupV4?.containsCustomData() ?: false
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GroupVersioned
if (pwGroupV3 != other.pwGroupV3) return false
if (pwGroupV4 != other.pwGroupV4) return false
return true
}
override fun hashCode(): Int {
var result = pwGroupV3?.hashCode() ?: 0
result = 31 * result + (pwGroupV4?.hashCode() ?: 0)
return result
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
package com.kunzisoft.keepass.database.element
class MemoryProtectionConfig {

View File

@@ -17,19 +17,17 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant
package com.kunzisoft.keepass.database.element
interface NodeTimeInterface {
var creationTime: DateInstant
var creationTime: PwDate
var lastModificationTime: DateInstant
var lastModificationTime: PwDate
var lastAccessTime: DateInstant
var lastAccessTime: PwDate
var expiryTime: DateInstant
var expiryTime: PwDate
var expires: Boolean

View File

@@ -1,10 +1,8 @@
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.Group
interface NodeVersioned: PwNodeInterface<GroupVersioned> {
interface Node: NodeVersionedInterface<Group> {
val nodeId: NodeId<*>?
val nodeId: PwNodeId<*>?
val nodePositionInParent: Int
get() {
@@ -17,7 +15,7 @@ interface Node: NodeVersionedInterface<Group> {
return -1
}
fun addParentFrom(node: Node) {
fun addParentFrom(node: NodeVersioned) {
parent = node.parent
}

View File

@@ -17,33 +17,20 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
import com.kunzisoft.keepass.database.ObjectNameResource
// Note: We can get away with using int's to store unsigned 32-bit ints
// since we won't do arithmetic on these values (also unlikely to
// reach negative ids).
enum class CompressionAlgorithm : ObjectNameResource, Parcelable {
enum class PwCompressionAlgorithm : ObjectNameResource {
None,
GZip;
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeEnum(this)
}
override fun describeContents(): Int {
return 0
}
override fun getName(resources: Resources): String {
return when (this) {
None -> resources.getString(R.string.compression_none)
@@ -51,14 +38,4 @@ enum class CompressionAlgorithm : ObjectNameResource, Parcelable {
}
}
companion object CREATOR : Parcelable.Creator<CompressionAlgorithm> {
override fun createFromParcel(parcel: Parcel): CompressionAlgorithm {
return parcel.readEnum<CompressionAlgorithm>() ?: None
}
override fun newArray(size: Int): Array<CompressionAlgorithm?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -17,32 +17,26 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
import org.apache.commons.io.IOUtils
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException
import com.kunzisoft.keepass.utils.MemoryUtil
import java.io.*
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
abstract class DatabaseVersioned<
abstract class PwDatabase<
GroupId,
EntryId,
Group : GroupVersioned<GroupId, EntryId, Group, Entry>,
Entry : EntryVersioned<GroupId, EntryId, Group, Entry>
Group : PwGroup<GroupId, EntryId, Group, Entry>,
Entry : PwEntry<GroupId, EntryId, Group, Entry>
> {
// Algorithm used to encrypt the database
protected var algorithm: EncryptionAlgorithm? = null
protected var algorithm: PwEncryptionAlgorithm? = null
abstract val kdfEngine: KdfEngine?
@@ -52,13 +46,13 @@ abstract class DatabaseVersioned<
var finalKey: ByteArray? = null
protected set
var iconFactory = IconImageFactory()
var iconFactory = PwIconFactory()
protected set
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
private var groupIndexes = LinkedHashMap<PwNodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<PwNodeId<EntryId>, Entry>()
abstract val version: String
@@ -66,15 +60,15 @@ abstract class DatabaseVersioned<
abstract var numberKeyEncryptionRounds: Long
var encryptionAlgorithm: EncryptionAlgorithm
var encryptionAlgorithm: PwEncryptionAlgorithm
get() {
return algorithm ?: EncryptionAlgorithm.AESRijndael
return algorithm ?: PwEncryptionAlgorithm.AESRijndael
}
set(algorithm) {
this.algorithm = algorithm
}
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
abstract val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
var rootGroup: Group? = null
@@ -130,7 +124,7 @@ abstract class DatabaseVersioned<
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyByteArrayOutputStream = ByteArrayOutputStream()
IOUtils.copy(keyInputStream, keyByteArrayOutputStream)
MemoryUtil.copyStream(keyInputStream, keyByteArrayOutputStream)
val keyData = keyByteArrayOutputStream.toByteArray()
val keyByteArrayInputStream = ByteArrayInputStream(keyData)
@@ -140,7 +134,7 @@ abstract class DatabaseVersioned<
}
when (keyData.size.toLong()) {
0L -> throw KeyFileEmptyDatabaseException()
0L -> throw LoadDatabaseKeyFileEmptyException()
32L -> return keyData
64L -> try {
return hexStringToByteArray(String(keyData))
@@ -198,9 +192,9 @@ abstract class DatabaseVersioned<
* -------------------------------------
*/
abstract fun newGroupId(): NodeId<GroupId>
abstract fun newGroupId(): PwNodeId<GroupId>
abstract fun newEntryId(): NodeId<EntryId>
abstract fun newEntryId(): PwNodeId<EntryId>
abstract fun createGroup(): Group
@@ -225,7 +219,7 @@ abstract class DatabaseVersioned<
* ID number to check for
* @return True if the ID is used, false otherwise
*/
fun isGroupIdUsed(id: NodeId<GroupId>): Boolean {
fun isGroupIdUsed(id: PwNodeId<GroupId>): Boolean {
return groupIndexes.containsKey(id)
}
@@ -240,7 +234,7 @@ abstract class DatabaseVersioned<
}
}
fun getGroupById(id: NodeId<GroupId>): Group? {
fun getGroupById(id: PwNodeId<GroupId>): Group? {
return this.groupIndexes[id]
}
@@ -253,7 +247,7 @@ abstract class DatabaseVersioned<
group.parent?.addChildGroup(group)
this.groupIndexes[newGroupId] = group
} else {
throw DuplicateUuidDatabaseException(Type.GROUP, groupId)
throw LoadDatabaseDuplicateUuidException(Type.GROUP, groupId)
}
} else {
this.groupIndexes[groupId] = group
@@ -281,7 +275,7 @@ abstract class DatabaseVersioned<
}
}
fun isEntryIdUsed(id: NodeId<EntryId>): Boolean {
fun isEntryIdUsed(id: PwNodeId<EntryId>): Boolean {
return entryIndexes.containsKey(id)
}
@@ -289,7 +283,7 @@ abstract class DatabaseVersioned<
return entryIndexes.values
}
fun getEntryById(id: NodeId<EntryId>): Entry? {
fun getEntryById(id: PwNodeId<EntryId>): Entry? {
return this.entryIndexes[id]
}
@@ -302,7 +296,7 @@ abstract class DatabaseVersioned<
entry.parent?.addChildEntry(entry)
this.entryIndexes[newEntryId] = entry
} else {
throw DuplicateUuidDatabaseException(Type.ENTRY, entryId)
throw LoadDatabaseDuplicateUuidException(Type.ENTRY, entryId)
}
} else {
this.entryIndexes[entryId] = entry
@@ -382,19 +376,19 @@ abstract class DatabaseVersioned<
addEntryTo(entry, origParent)
}
abstract fun isInRecycleBin(group: Group): Boolean
abstract fun isBackup(group: Group): Boolean
fun isGroupSearchable(group: Group?, omitBackup: Boolean): Boolean {
if (group == null)
return false
if (omitBackup && isInRecycleBin(group))
if (omitBackup && isBackup(group))
return false
return true
}
companion object {
private const val TAG = "DatabaseVersioned"
private const val TAG = "PwDatabase"
val UUID_ZERO = UUID(0, 0)

View File

@@ -17,17 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException
import java.io.InputStream
@@ -37,12 +31,10 @@ import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
private var numKeyEncRounds: Int = 0
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override val version: String
@@ -52,32 +44,22 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
kdfListV3.add(KdfFactory.aesKdf)
}
private fun getGroupById(groupId: Int): GroupKDB? {
if (groupId == -1)
return null
return getGroupById(NodeIdInt(groupId))
}
// Retrieve backup group in index
val backupGroup: GroupKDB?
get() = if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) null else getGroupById(backupGroupId)
override val kdfEngine: KdfEngine?
get() = kdfListV3[0]
override val kdfAvailableList: List<KdfEngine>
get() = kdfListV3
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() {
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
val list = ArrayList<PwEncryptionAlgorithm>()
list.add(PwEncryptionAlgorithm.AESRijndael)
return list
}
val rootGroups: List<GroupKDB>
val rootGroups: List<PwGroupV3>
get() {
val kids = ArrayList<GroupKDB>()
val kids = ArrayList<PwGroupV3>()
doForEachGroupInIndex { group ->
if (group.level == 0)
kids.add(group)
@@ -99,7 +81,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
}
init {
algorithm = EncryptionAlgorithm.AESRijndael
algorithm = PwEncryptionAlgorithm.AESRijndael
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS
}
@@ -108,10 +90,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
*
* @return new tree id
*/
override fun newGroupId(): NodeIdInt {
var newId: NodeIdInt
override fun newGroupId(): PwNodeIdInt {
var newId: PwNodeIdInt
do {
newId = NodeIdInt()
newId = PwNodeIdInt()
} while (isGroupIdUsed(newId))
return newId
@@ -122,10 +104,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
*
* @return new tree id
*/
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
override fun newEntryId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
do {
newId = NodeIdUUID()
newId = PwNodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
@@ -170,12 +152,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return null
}
override fun createGroup(): GroupKDB {
return GroupKDB()
override fun createGroup(): PwGroupV3 {
return PwGroupV3()
}
override fun createEntry(): EntryKDB {
return EntryKDB()
override fun createEntry(): PwEntryV3 {
return PwEntryV3()
}
override fun rootCanContainsEntry(): Boolean {
@@ -186,16 +168,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return false
}
override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group
if (currentGroup == backupGroup)
return true
override fun isBackup(group: PwGroupV3): Boolean {
var currentGroup: PwGroupV3? = group
while (currentGroup != null) {
if (currentGroup.level == 0
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
backupGroupId = currentGroup.id
if (currentGroup.level == 0 && currentGroup.title.equals("Backup", ignoreCase = true)) {
return true
}
currentGroup = currentGroup.parent
@@ -203,68 +179,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return false
}
/**
* Ensure that the recycle bin tree exists, if enabled and create it
* if it doesn't exist
*/
fun ensureRecycleBinExists() {
rootGroups.forEach { currentGroup ->
if (currentGroup.level == 0
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
backupGroupId = currentGroup.id
}
}
if (backupGroup == null) {
// Create recycle bin
val recycleBinGroup = createGroup().apply {
title = BACKUP_FOLDER_TITLE
icon = iconFactory.trashIcon
}
addGroupTo(recycleBinGroup, rootGroup)
backupGroupId = recycleBinGroup.id
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
// TODO #394 Backup pw3
return true
}
fun recycle(group: GroupKDB) {
ensureRecycleBinExists()
removeGroupFrom(group, group.parent)
addGroupTo(group, backupGroup)
group.afterAssignNewParent()
}
fun recycle(entry: EntryKDB) {
ensureRecycleBinExists()
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, backupGroup)
entry.afterAssignNewParent()
}
fun undoRecycle(group: GroupKDB, origParent: GroupKDB) {
removeGroupFrom(group, backupGroup)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: EntryKDB, origParent: GroupKDB) {
removeEntryFrom(entry, backupGroup)
addEntryTo(entry, origParent)
}
companion object {
const val BACKUP_FOLDER_TITLE = "Backup"
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
/**

View File

@@ -17,30 +17,17 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import biz.source_code.base64Coder.Base64Coder
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.crypto.keyDerivation.*
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node
import org.w3c.dom.Text
@@ -54,30 +41,29 @@ import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
var hmacKey: ByteArray? = null
private set
var dataCipher = AesEngine.CIPHER_UUID
private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = CompressionAlgorithm.GZip
var compressionAlgorithm = PwCompressionAlgorithm.GZip
var kdfParameters: KdfParameters? = null
private var kdfList: MutableList<KdfEngine> = ArrayList()
private var kdfV4List: MutableList<KdfEngine> = ArrayList()
private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary()
var kdbxVersion: Long = 0
var name = ""
var nameChanged = DateInstant()
var name = "KeePass DX database"
var nameChanged = PwDate()
// TODO change setting date
var settingsChanged = DateInstant()
var settingsChanged = PwDate()
var description = ""
var descriptionChanged = DateInstant()
var descriptionChanged = PwDate()
var defaultUserName = ""
var defaultUserNameChanged = DateInstant()
var defaultUserNameChanged = PwDate()
// TODO date
var keyLastChanged = DateInstant()
var keyLastChanged = PwDate()
var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1
var isKeyChangeForceOnce = false
@@ -92,68 +78,57 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var recycleBinUUID: UUID = UUID_ZERO
var recycleBinChanged = Date()
var entryTemplatesGroup = UUID_ZERO
var entryTemplatesGroupChanged = DateInstant()
var entryTemplatesGroupChanged = PwDate()
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
var historyMaxSize = DEFAULT_HISTORY_MAX_SIZE
var lastSelectedGroupUUID = UUID_ZERO
var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<DeletedObject>()
val customIcons = ArrayList<IconImageCustom>()
val deletedObjects = ArrayList<PwDeletedObject>()
val customIcons = ArrayList<PwIconCustom>()
val customData = HashMap<String, String>()
var binaryPool = BinaryPool()
var binPool = BinaryPool()
var localizedAppName = "KeePassDX"
var localizedAppName = "KeePassDX" // TODO resource
init {
kdfList.add(KdfFactory.aesKdf)
kdfList.add(KdfFactory.argon2Kdf)
kdfV4List.add(KdfFactory.aesKdf)
kdfV4List.add(KdfFactory.argon2Kdf)
}
constructor()
/**
* Create a new database with a root group
*/
constructor(databaseName: String, rootName: String) {
name = databaseName
val group = createGroup().apply {
title = rootName
constructor(databaseName: String) {
val groupV4 = createGroup().apply {
title = databaseName
icon = iconFactory.folderIcon
}
rootGroup = group
addGroupIndex(group)
rootGroup = groupV4
addGroupIndex(groupV4)
}
override val version: String
get() {
val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1"
FILE_VERSION_32_4 -> "4.0"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
}
get() = "KeePass 2"
override val kdfEngine: KdfEngine?
get() = try {
getEngineKDBX4(kdfParameters)
getEngineV4(kdfParameters)
} catch (unknownKDF: UnknownKDF) {
Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF)
null
}
override val kdfAvailableList: List<KdfEngine>
get() = kdfList
get() = kdfV4List
@Throws(UnknownKDF::class)
fun getEngineKDBX4(kdfParameters: KdfParameters?): KdfEngine {
fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine {
val unknownKDFException = UnknownKDF()
if (kdfParameters == null) {
throw unknownKDFException
}
for (engine in kdfList) {
for (engine in kdfV4List) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
@@ -161,53 +136,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
throw unknownKDFException
}
val availableCompressionAlgorithms: List<CompressionAlgorithm>
val availableCompressionAlgorithms: List<PwCompressionAlgorithm>
get() {
val list = ArrayList<CompressionAlgorithm>()
list.add(CompressionAlgorithm.None)
list.add(CompressionAlgorithm.GZip)
val list = ArrayList<PwCompressionAlgorithm>()
list.add(PwCompressionAlgorithm.None)
list.add(PwCompressionAlgorithm.GZip)
return list
}
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
binaryPool.doForEachBinary { key, binary ->
try {
when (oldCompression) {
CompressionAlgorithm.None -> {
when (newCompression) {
CompressionAlgorithm.None -> {
}
CompressionAlgorithm.GZip -> {
// To compress, create a new binary with file
binary.compress()
}
}
}
CompressionAlgorithm.GZip -> {
when (newCompression) {
CompressionAlgorithm.None -> {
// To decompress, create a new binary with file
binary.decompress()
}
CompressionAlgorithm.GZip -> {
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to change compression for $key")
}
}
}
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() {
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
list.add(EncryptionAlgorithm.ChaCha20)
val list = ArrayList<PwEncryptionAlgorithm>()
list.add(PwEncryptionAlgorithm.AESRijndael)
list.add(PwEncryptionAlgorithm.Twofish)
list.add(PwEncryptionAlgorithm.ChaCha20)
return list
}
@@ -255,31 +197,31 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
override val passwordEncoding: String
get() = "UTF-8"
private fun getGroupByUUID(groupUUID: UUID): GroupKDBX? {
fun getGroupByUUID(groupUUID: UUID): PwGroupV4? {
if (groupUUID == UUID_ZERO)
return null
return getGroupById(NodeIdUUID(groupUUID))
return getGroupById(PwNodeIdUUID(groupUUID))
}
// Retrieve recycle bin in index
val recycleBin: GroupKDBX?
get() = if (recycleBinUUID == UUID_ZERO) null else getGroupByUUID(recycleBinUUID)
val recycleBin: PwGroupV4?
get() = getGroupByUUID(recycleBinUUID)
val lastSelectedGroup: GroupKDBX?
val lastSelectedGroup: PwGroupV4?
get() = getGroupByUUID(lastSelectedGroupUUID)
val lastTopVisibleGroup: GroupKDBX?
val lastTopVisibleGroup: PwGroupV4?
get() = getGroupByUUID(lastTopVisibleGroupUUID)
fun setDataEngine(dataEngine: CipherEngine) {
this.dataEngine = dataEngine
}
fun getCustomIcons(): List<IconImageCustom> {
fun getCustomIcons(): List<PwIconCustom> {
return customIcons
}
fun addCustomIcon(customIcon: IconImageCustom) {
fun addCustomIcon(customIcon: PwIconCustom) {
this.customIcons.add(customIcon)
}
@@ -322,7 +264,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
fun makeFinalKey(masterSeed: ByteArray) {
kdfParameters?.let { keyDerivationFunctionParameters ->
val kdfEngine = getEngineKDBX4(keyDerivationFunctionParameters)
val kdfEngine = getEngineV4(keyDerivationFunctionParameters)
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
@@ -384,7 +326,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val text = children2.item(k)
if (text.nodeType == Node.TEXT_NODE) {
val txt = text as Text
return Base64.decode(txt.nodeValue, BASE_64_FLAG)
return Base64Coder.decode(txt.nodeValue)
}
}
}
@@ -398,42 +340,42 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return null
}
override fun newGroupId(): NodeIdUUID {
var newId: NodeIdUUID
override fun newGroupId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
do {
newId = NodeIdUUID()
newId = PwNodeIdUUID()
} while (isGroupIdUsed(newId))
return newId
}
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
override fun newEntryId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
do {
newId = NodeIdUUID()
newId = PwNodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
}
override fun createGroup(): GroupKDBX {
return GroupKDBX()
override fun createGroup(): PwGroupV4 {
return PwGroupV4()
}
override fun createEntry(): EntryKDBX {
return EntryKDBX()
override fun createEntry(): PwEntryV4 {
return PwEntryV4()
}
override fun rootCanContainsEntry(): Boolean {
return true
}
override fun isInRecycleBin(group: GroupKDBX): Boolean {
override fun isBackup(group: PwGroupV4): Boolean {
// To keep compatibility with old V1 databases
var currentGroup: GroupKDBX? = group
var currentGroup: PwGroupV4? = group
while (currentGroup != null) {
if (currentGroup.parent == rootGroup
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
&& currentGroup.title.equals("Backup", ignoreCase = true)) {
return true
}
currentGroup = currentGroup.parent
@@ -451,7 +393,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
* Ensure that the recycle bin tree exists, if enabled and create it
* if it doesn't exist
*/
fun ensureRecycleBinExists(resources: Resources) {
private fun ensureRecycleBin(resources: Resources) {
if (recycleBin == null) {
// Create recycle bin
val recycleBinGroup = createGroup().apply {
@@ -467,68 +409,61 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
}
fun removeRecycleBin() {
if (recycleBin != null) {
recycleBinUUID = UUID_ZERO
recycleBinChanged = DateInstant().date
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: NodeVersioned<*, GroupKDBX, EntryKDBX>): Boolean {
fun canRecycle(node: PwNode<*, PwGroupV4, PwEntryV4>): Boolean {
if (!isRecycleBinEnabled)
return false
if (recycleBin == null)
return false
return true
if (!node.isContainedIn(recycleBin!!))
return true
return false
}
fun recycle(group: GroupKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
fun recycle(group: PwGroupV4, resources: Resources) {
ensureRecycleBin(resources)
removeGroupFrom(group, group.parent)
addGroupTo(group, recycleBin)
group.afterAssignNewParent()
}
fun recycle(entry: EntryKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
fun recycle(entry: PwEntryV4, resources: Resources) {
ensureRecycleBin(resources)
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, recycleBin)
entry.afterAssignNewParent()
}
fun undoRecycle(group: GroupKDBX, origParent: GroupKDBX) {
fun undoRecycle(group: PwGroupV4, origParent: PwGroupV4) {
removeGroupFrom(group, recycleBin)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: EntryKDBX, origParent: GroupKDBX) {
fun undoRecycle(entry: PwEntryV4, origParent: PwGroupV4) {
removeEntryFrom(entry, recycleBin)
addEntryTo(entry, origParent)
}
fun getDeletedObjects(): List<DeletedObject> {
fun getDeletedObjects(): List<PwDeletedObject> {
return deletedObjects
}
fun addDeletedObject(deletedObject: DeletedObject) {
fun addDeletedObject(deletedObject: PwDeletedObject) {
this.deletedObjects.add(deletedObject)
}
override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) {
override fun removeEntryFrom(entryToRemove: PwEntryV4, parent: PwGroupV4?) {
super.removeEntryFrom(entryToRemove, parent)
deletedObjects.add(DeletedObject(entryToRemove.id))
deletedObjects.add(PwDeletedObject(entryToRemove.id))
}
override fun undoDeleteEntryFrom(entry: EntryKDBX, origParent: GroupKDBX?) {
override fun undoDeleteEntryFrom(entry: PwEntryV4, origParent: PwGroupV4?) {
super.undoDeleteEntryFrom(entry, origParent)
deletedObjects.remove(DeletedObject(entry.id))
deletedObjects.remove(PwDeletedObject(entry.id))
}
fun containsPublicCustomData(): Boolean {
@@ -542,16 +477,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
override fun clearCache() {
try {
super.clearCache()
binaryPool.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}
super.clearCache()
binPool.clear()
}
companion object {
private val TAG = DatabaseKDBX::class.java.name
private val TAG = PwDatabaseV4::class.java.name
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
@@ -561,9 +492,5 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
//private const val VersionElementName = "Version";
private const val KeyElementName = "Key"
private const val KeyDataElementName = "Data"
const val BASE_64_FLAG = Base64.DEFAULT
const val BUFFER_SIZE_BYTES = 3 * 128
}
}

View File

@@ -17,12 +17,12 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file
package com.kunzisoft.keepass.database.element
import java.text.SimpleDateFormat
import java.util.*
object DatabaseKDBXXML {
object PwDatabaseV4XML {
const val ElemDocNode = "KeePassFile"
const val ElemMeta = "Meta"

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import com.kunzisoft.keepass.utils.Types
import java.util.*
/**
* Converting from the C Date format to the Java data format is
* expensive when done for every record at once.
*/
class PwDate : Parcelable {
private var jDate: Date = Date()
private var jDateBuilt = false
@Transient
private var cDate: ByteArray? = null
@Transient
private var cDateBuilt = false
val date: Date
get() {
if (!jDateBuilt) {
jDate = readTime(cDate, 0, calendar)
jDateBuilt = true
}
return jDate
}
val byteArrayDate: ByteArray?
get() {
if (!cDateBuilt) {
cDate = writeTime(jDate, calendar)
cDateBuilt = true
}
return cDate
}
constructor(buf: ByteArray, offset: Int) {
cDate = ByteArray(DATE_SIZE)
System.arraycopy(buf, offset, cDate!!, 0, DATE_SIZE)
cDateBuilt = true
}
constructor(source: PwDate) {
this.jDate = Date(source.jDate.time)
this.jDateBuilt = source.jDateBuilt
if (source.cDate != null) {
val dateLength = source.cDate!!.size
this.cDate = ByteArray(dateLength)
System.arraycopy(source.cDate!!, 0, this.cDate!!, 0, dateLength)
}
this.cDateBuilt = source.cDateBuilt
}
constructor(date: Date) {
jDate = Date(date.time)
jDateBuilt = true
}
constructor(millis: Long) {
jDate = Date(millis)
jDateBuilt = true
}
constructor() {
jDate = Date()
jDateBuilt = true
}
protected constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as Date
jDateBuilt = parcel.readByte().toInt() != 0
cDateBuilt = false
}
override fun describeContents(): Int {
return 0
}
fun getDateTimeString(resources: Resources): String {
return Companion.getDateTimeString(resources, this.date)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(date)
dest.writeByte((if (jDateBuilt) 1 else 0).toByte())
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null) {
return false
}
if (javaClass != other.javaClass) {
return false
}
val date = other as PwDate?
return if (cDateBuilt && date!!.cDateBuilt) {
Arrays.equals(cDate, date.cDate)
} else if (jDateBuilt && date!!.jDateBuilt) {
isSameDate(jDate, date.jDate)
} else if (cDateBuilt && date!!.jDateBuilt) {
Arrays.equals(date.byteArrayDate, cDate)
} else {
isSameDate(date!!.date, jDate)
}
}
override fun hashCode(): Int {
var result = jDate.hashCode()
result = 31 * result + jDateBuilt.hashCode()
result = 31 * result + (cDate?.contentHashCode() ?: 0)
result = 31 * result + cDateBuilt.hashCode()
return result
}
companion object {
private const val DATE_SIZE = 5
private var mCalendar: Calendar? = null
val NEVER_EXPIRE = neverExpire
private val calendar: Calendar?
get() {
if (mCalendar == null) {
mCalendar = Calendar.getInstance()
}
return mCalendar
}
private val neverExpire: PwDate
get() {
val 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 PwDate(cal.time)
}
@JvmField
val CREATOR: Parcelable.Creator<PwDate> = object : Parcelable.Creator<PwDate> {
override fun createFromParcel(parcel: Parcel): PwDate {
return PwDate(parcel)
}
override fun newArray(size: Int): Array<PwDate?> {
return arrayOfNulls(size)
}
}
/**
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
* to a java.util.Date instance.
*/
fun readTime(buf: ByteArray?, offset: Int, calendar: Calendar?): Date {
var time = calendar
val dw1 = Types.readUByte(buf!!, offset)
val dw2 = Types.readUByte(buf, offset + 1)
val dw3 = Types.readUByte(buf, offset + 2)
val dw4 = Types.readUByte(buf, offset + 3)
val dw5 = Types.readUByte(buf, offset + 4)
// Unpack 5 byte structure to date and time
val year = dw1 shl 6 or (dw2 shr 2)
val month = dw2 and 0x00000003 shl 2 or (dw3 shr 6)
val day = dw3 shr 1 and 0x0000001F
val hour = dw3 and 0x00000001 shl 4 or (dw4 shr 4)
val minute = dw4 and 0x0000000F shl 2 or (dw5 shr 6)
val second = dw5 and 0x0000003F
if (time == null) {
time = Calendar.getInstance()
}
// File format is a 1 based month, java Calendar uses a zero based month
// File format is a 1 based day, java Calendar uses a 1 based day
time!!.set(year, month - 1, day, hour, minute, second)
return time.time
}
@JvmOverloads
fun writeTime(date: Date?, calendar: Calendar? = null): ByteArray? {
var cal = calendar
if (date == null) {
return null
}
val buf = ByteArray(5)
if (cal == null) {
cal = Calendar.getInstance()
}
cal!!.time = date
val year = cal.get(Calendar.YEAR)
// File format is a 1 based month, java Calendar uses a zero based month
val month = cal.get(Calendar.MONTH) + 1
// File format is a 0 based day, java Calendar uses a 1 based day
val day = cal.get(Calendar.DAY_OF_MONTH) - 1
val hour = cal.get(Calendar.HOUR_OF_DAY)
val minute = cal.get(Calendar.MINUTE)
val second = cal.get(Calendar.SECOND)
buf[0] = Types.writeUByte(year shr 6 and 0x0000003F)
buf[1] = Types.writeUByte(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003))
buf[2] = (month and 0x00000003 shl 6
or (day and 0x0000001F shl 1) or (hour shr 4 and 0x00000001)).toByte()
buf[3] = (hour and 0x0000000F shl 4 or (minute shr 2 and 0x0000000F)).toByte()
buf[4] = (minute and 0x00000003 shl 6 or (second and 0x0000003F)).toByte()
return buf
}
private fun isSameDate(d1: Date?, d2: Date?): Boolean {
val cal1 = Calendar.getInstance()
cal1.time = d1
cal1.set(Calendar.MILLISECOND, 0)
val cal2 = Calendar.getInstance()
cal2.time = d2
cal2.set(Calendar.MILLISECOND, 0)
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) &&
cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) &&
cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
}
fun getDateTimeString(resources: Resources, date: Date): String {
return java.text.DateFormat.getDateTimeInstance(
java.text.DateFormat.MEDIUM,
java.text.DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(date)
}
}
}

View File

@@ -19,13 +19,12 @@
*/
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.Date
import java.util.UUID
class DeletedObject {
class PwDeletedObject {
var uuid: UUID = DatabaseVersioned.UUID_ZERO
var uuid: UUID = PwDatabase.UUID_ZERO
var deletionTime: Date? = null
get() = if (field == null) {
Date(System.currentTimeMillis())
@@ -44,7 +43,7 @@ class DeletedObject {
return true
if (other == null)
return false
if (other !is DeletedObject)
if (other !is PwDeletedObject)
return false
return uuid == other.uuid
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
@@ -26,11 +26,11 @@ import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.database.ObjectNameResource
import java.util.UUID
enum class EncryptionAlgorithm : ObjectNameResource {
enum class PwEncryptionAlgorithm : ObjectNameResource {
AESRijndael,
Twofish,

View File

@@ -0,0 +1,19 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import java.util.*
abstract class PwEntry
<
GroupId,
EntryId,
ParentGroup: PwGroup<GroupId, EntryId, ParentGroup, Entry>,
Entry: PwEntry<GroupId, EntryId, ParentGroup, Entry>
>
: PwNode<EntryId, ParentGroup, Entry>, PwEntryInterface<ParentGroup> {
constructor() : super()
constructor(parcel: Parcel) : super(parcel)
}

View File

@@ -0,0 +1,12 @@
package com.kunzisoft.keepass.database.element
interface PwEntryInterface<ParentGroup> : PwNodeInterface<ParentGroup> {
var username: String
var password: String
var url: String
var notes: String
}

View File

@@ -17,16 +17,15 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.entry
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
import java.io.UnsupportedEncodingException
import java.util.Arrays
import java.util.UUID
/**
* Structure containing information about one entry.
@@ -49,7 +48,7 @@ import java.util.*
* @author Dominik Reichl <dominik.reichl></dominik.reichl>@t-online.de>
* @author Jeremy Jamet <jeremy.jamet></jeremy.jamet>@kunzisoft.com>
*/
class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
class PwEntryV3 : PwEntry<Int, UUID, PwGroupV3, PwEntryV3>, PwNodeV3Interface {
/** A string describing what is in pBinaryData */
var binaryDesc = ""
@@ -57,11 +56,12 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
* @return the actual binaryData byte array.
*/
var binaryData: ByteArray = ByteArray(0)
private set
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
if (binaryData.contentEquals(ByteArray(0))) return false
if (Arrays.equals(binaryData, ByteArray(0))) return false
if (notes.isEmpty()) return false
if (binaryDesc != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
@@ -72,12 +72,12 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
override fun initNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
override fun copyNodeId(nodeId: PwNodeId<UUID>): PwNodeId<UUID> {
return PwNodeIdUUID(nodeId.id)
}
constructor() : super()
@@ -85,19 +85,18 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
constructor(parcel: Parcel) : super(parcel) {
title = parcel.readString() ?: title
username = parcel.readString() ?: username
password = parcel.readString() ?: password
parcel.readByteArray(passwordBytes)
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDesc = parcel.readString() ?: binaryDesc
binaryData = ByteArray(parcel.readInt())
parcel.readByteArray(binaryData)
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
return parcel.readParcelable(GroupKDB::class.java.classLoader)
override fun readParentParcelable(parcel: Parcel): PwGroupV3? {
return parcel.readParcelable(PwGroupV3::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) {
override fun writeParentParcelable(parent: PwGroupV3?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
@@ -105,19 +104,22 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
super.writeToParcel(dest, flags)
dest.writeString(title)
dest.writeString(username)
dest.writeString(password)
dest.writeByteArray(passwordBytes)
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDesc)
dest.writeInt(binaryData.size)
dest.writeByteArray(binaryData)
}
fun updateWith(source: EntryKDB) {
fun updateWith(source: PwEntryV3) {
super.updateWith(source)
title = source.title
username = source.username
password = source.password
val passLen = source.passwordBytes.size
passwordBytes = ByteArray(passLen)
System.arraycopy(source.passwordBytes, 0, passwordBytes, 0, passLen)
url = source.url
notes = source.notes
binaryDesc = source.binaryDesc
@@ -129,10 +131,32 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override var username = ""
var passwordBytes: ByteArray = ByteArray(0)
private set
/** Securely erase old password before copying new. */
fun setPassword(buf: ByteArray, offset: Int, len: Int) {
fill(passwordBytes, 0.toByte())
passwordBytes = ByteArray(len)
System.arraycopy(buf, offset, passwordBytes, 0, len)
}
/**
* @return the actual password byte array.
*/
override var password = ""
override var password: String
get() = String(passwordBytes)
set(pass) {
var password: ByteArray
try {
password = pass.toByteArray(charset("UTF-8"))
setPassword(password, 0, password.size)
} catch (e: UnsupportedEncodingException) {
password = pass.toByteArray()
setPassword(password, 0, password.size)
}
}
override var url = ""
@@ -143,6 +167,13 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override val type: Type
get() = Type.ENTRY
fun setBinaryData(buf: ByteArray, offset: Int, len: Int) {
/** Securely erase old data before copying new. */
fill(binaryData, 0.toByte())
binaryData = ByteArray(len)
System.arraycopy(buf, offset, binaryData, 0, len)
}
companion object {
/** Size of byte buffer needed to hold this struct. */
@@ -152,14 +183,22 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
private const val PMS_ID_URL = "$"
@JvmField
val CREATOR: Parcelable.Creator<EntryKDB> = object : Parcelable.Creator<EntryKDB> {
override fun createFromParcel(parcel: Parcel): EntryKDB {
return EntryKDB(parcel)
val CREATOR: Parcelable.Creator<PwEntryV3> = object : Parcelable.Creator<PwEntryV3> {
override fun createFromParcel(`in`: Parcel): PwEntryV3 {
return PwEntryV3(`in`)
}
override fun newArray(size: Int): Array<EntryKDB?> {
override fun newArray(size: Int): Array<PwEntryV3?> {
return arrayOfNulls(size)
}
}
/**
* fill byte array
*/
private fun fill(array: ByteArray, value: Byte) {
for (i in array.indices)
array[i] = value
}
}
}

View File

@@ -17,34 +17,24 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.MemoryUtil
import java.util.*
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
// To decode each field not parcelable
@Transient
private var mDatabase: DatabaseKDBX? = null
private var mDatabase: PwDatabaseV4? = null
@Transient
private var mDecodeRef = false
override var icon: IconImage
override var icon: PwIcon
get() {
return when {
iconCustom.isUnknown -> super.icon
@@ -52,19 +42,19 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
}
}
set(value) {
if (value is IconImageStandard)
iconCustom = IconImageCustom.UNKNOWN_ICON
if (value is PwIconStandard)
iconCustom = PwIconCustom.UNKNOWN_ICON
super.icon = value
}
var iconCustom = IconImageCustom.UNKNOWN_ICON
var iconCustom = PwIconCustom.UNKNOWN_ICON
private var customData = HashMap<String, String>()
var fields = HashMap<String, ProtectedString>()
var binaries = HashMap<String, BinaryAttachment>()
val binaries = HashMap<String, ProtectedBinary>()
var foregroundColor = ""
var backgroundColor = ""
var overrideURL = ""
var autoType = AutoType()
var history = ArrayList<EntryKDBX>()
var history = ArrayList<PwEntryV4>()
var additional = ""
var tags = ""
@@ -103,12 +93,12 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
iconCustom = parcel.readParcelable(PwIconCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong()
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java)
locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged
customData = MemoryUtil.readStringParcelableMap(parcel)
fields = MemoryUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
// TODO binaries = MemoryUtil.readStringParcelableMap(parcel, ProtectedBinary.class);
foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL
@@ -124,9 +114,9 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount)
dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
MemoryUtil.writeStringParcelableMap(dest, customData)
MemoryUtil.writeStringParcelableMap(dest, flags, fields)
// TODO MemoryUtil.writeStringParcelableMap(dest, flags, binaries);
dest.writeString(foregroundColor)
dest.writeString(backgroundColor)
dest.writeString(overrideURL)
@@ -141,11 +131,11 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
* Update with deep copy of each entry element
* @param source
*/
fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) {
fun updateWith(source: PwEntryV4, copyHistory: Boolean = true) {
super.updateWith(source)
iconCustom = IconImageCustom(source.iconCustom)
iconCustom = PwIconCustom(source.iconCustom)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
locationChanged = PwDate(source.locationChanged)
// Add all custom elements in map
customData.clear()
customData.putAll(source.customData)
@@ -165,7 +155,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
tags = source.tags
}
fun startToManageFieldReferences(db: DatabaseKDBX) {
fun startToManageFieldReferences(db: PwDatabaseV4) {
this.mDatabase = db
this.mDecodeRef = true
}
@@ -175,24 +165,24 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
this.mDecodeRef = false
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
override fun initNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
override fun copyNodeId(nodeId: PwNodeId<UUID>): PwNodeId<UUID> {
return PwNodeIdUUID(nodeId.id)
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
override fun readParentParcelable(parcel: Parcel): PwGroupV4? {
return parcel.readParcelable(PwGroupV4::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
override fun writeParentParcelable(parent: PwGroupV4?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
/**
* Decode a reference key with the FieldReferencesEngine
* Decode a reference key with the SprEngineV4
* @param decodeRef
* @param key
* @return
@@ -200,7 +190,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
private fun decodeRefKey(decodeRef: Boolean, key: String): String {
return fields[key]?.toString()?.let { text ->
return if (decodeRef) {
if (mDatabase == null) text else FieldReferencesEngine().compile(text, this, mDatabase!!)
if (mDatabase == null) text else SprEngineV4().compile(text, this, mDatabase!!)
} else text
} ?: ""
}
@@ -245,10 +235,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var usageCount: Long = 0
override var locationChanged = DateInstant()
override var locationChanged = PwDate()
fun afterChangeParent() {
locationChanged = DateInstant()
locationChanged = PwDate()
}
private fun isStandardField(key: String): Boolean {
@@ -280,11 +270,11 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fields.clear()
}
fun putExtraField(label: String, value: ProtectedString) {
fun addExtraField(label: String, value: ProtectedString) {
fields[label] = value
}
fun putProtectedBinary(key: String, value: BinaryAttachment) {
fun putProtectedBinary(key: String, value: ProtectedBinary) {
binaries[key] = value
}
@@ -300,7 +290,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return customData.isNotEmpty()
}
fun addEntryToHistory(entry: EntryKDBX) {
fun addEntryToHistory(entry: PwEntryV4) {
history.add(entry)
}
@@ -340,12 +330,12 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
const val STR_NOTES = "Notes"
@JvmField
val CREATOR: Parcelable.Creator<EntryKDBX> = object : Parcelable.Creator<EntryKDBX> {
override fun createFromParcel(parcel: Parcel): EntryKDBX {
return EntryKDBX(parcel)
val CREATOR: Parcelable.Creator<PwEntryV4> = object : Parcelable.Creator<PwEntryV4> {
override fun createFromParcel(parcel: Parcel): PwEntryV4 {
return PwEntryV4(parcel)
}
override fun newArray(size: Int): Array<EntryKDBX?> {
override fun newArray(size: Int): Array<PwEntryV4?> {
return arrayOfNulls(size)
}
}

View File

@@ -1,17 +1,15 @@
package com.kunzisoft.keepass.database.element.group
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeVersioned
abstract class GroupVersioned
abstract class PwGroup
<
GroupId,
EntryId,
Group: GroupVersioned<GroupId, EntryId, Group, Entry>,
Entry: EntryVersioned<GroupId, EntryId, Group, Entry>
Group: PwGroup<GroupId, EntryId, Group, Entry>,
Entry: PwEntry<GroupId, EntryId, Group, Entry>
>
: NodeVersioned<GroupId, Group, Entry>, GroupVersionedInterface<Group, Entry> {
: PwNode<GroupId, Group, Entry>, PwGroupInterface<Group, Entry> {
private var titleGroup = ""
@Transient
@@ -30,7 +28,7 @@ abstract class GroupVersioned
dest.writeString(titleGroup)
}
protected fun updateWith(source: GroupVersioned<GroupId, EntryId, Group, Entry>) {
protected fun updateWith(source: PwGroup<GroupId, EntryId, Group, Entry>) {
super.updateWith(source)
titleGroup = source.titleGroup
childGroups.clear()
@@ -71,11 +69,6 @@ abstract class GroupVersioned
this.childEntries.remove(entry)
}
override fun removeChildren() {
this.childGroups.clear()
this.childEntries.clear()
}
override fun toString(): String {
return titleGroup
}

View File

@@ -1,9 +1,8 @@
package com.kunzisoft.keepass.database.element.group
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.NodeHandler
interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>, Entry> : NodeVersionedInterface<Group> {
interface PwGroupInterface<Group: PwGroupInterface<Group, Entry>, Entry> : PwNodeInterface<Group> {
fun getChildGroups(): MutableList<Group>
@@ -17,8 +16,6 @@ interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>,
fun removeChildEntry(entry: Entry)
fun removeChildren()
fun allowAddEntryIfIsRoot(): Boolean
fun doForEachChildAndForIt(entryHandler: NodeHandler<Entry>,

View File

@@ -18,18 +18,13 @@
*
*/
package com.kunzisoft.keepass.database.element.group
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
class PwGroupV3 : PwGroup<Int, UUID, PwGroupV3, PwEntryV3>, PwNodeV3Interface {
var level = 0 // short
/** Used by KeePass internally, don't use */
@@ -42,11 +37,11 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
flags = parcel.readInt()
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
return parcel.readParcelable(GroupKDB::class.java.classLoader)
override fun readParentParcelable(parcel: Parcel): PwGroupV3? {
return parcel.readParcelable(PwGroupV3::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) {
override fun writeParentParcelable(parent: PwGroupV3?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
@@ -56,7 +51,7 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeInt(flags)
}
fun updateWith(source: GroupKDB) {
fun updateWith(source: PwGroupV3) {
super.updateWith(source)
level = source.level
flags = source.flags
@@ -65,12 +60,12 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override val type: Type
get() = Type.GROUP
override fun initNodeId(): NodeId<Int> {
return NodeIdInt()
override fun initNodeId(): PwNodeId<Int> {
return PwNodeIdInt()
}
override fun copyNodeId(nodeId: NodeId<Int>): NodeId<Int> {
return NodeIdInt(nodeId.id)
override fun copyNodeId(nodeId: PwNodeId<Int>): PwNodeId<Int> {
return PwNodeIdInt(nodeId.id)
}
override fun afterAssignNewParent() {
@@ -79,7 +74,7 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
}
fun setGroupId(groupId: Int) {
this.nodeId = NodeIdInt(groupId)
this.nodeId = PwNodeIdInt(groupId)
}
override fun allowAddEntryIfIsRoot(): Boolean {
@@ -89,12 +84,12 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
companion object {
@JvmField
val CREATOR: Parcelable.Creator<GroupKDB> = object : Parcelable.Creator<GroupKDB> {
override fun createFromParcel(parcel: Parcel): GroupKDB {
return GroupKDB(parcel)
val CREATOR: Parcelable.Creator<PwGroupV3> = object : Parcelable.Creator<PwGroupV3> {
override fun createFromParcel(parcel: Parcel): PwGroupV3 {
return PwGroupV3(parcel)
}
override fun newArray(size: Int): Array<GroupKDB?> {
override fun newArray(size: Int): Array<PwGroupV3?> {
return arrayOfNulls(size)
}
}

View File

@@ -17,28 +17,18 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.HashMap
import java.util.UUID
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
class PwGroupV4 : PwGroup<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
// TODO Encapsulate
override var icon: IconImage
override var icon: PwIcon
get() {
return if (iconCustom.isUnknown)
super.icon
@@ -46,11 +36,11 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
iconCustom
}
set(value) {
if (value is IconImageStandard)
iconCustom = IconImageCustom.UNKNOWN_ICON
if (value is PwIconStandard)
iconCustom = PwIconCustom.UNKNOWN_ICON
super.icon = value
}
var iconCustom = IconImageCustom.UNKNOWN_ICON
var iconCustom = PwIconCustom.UNKNOWN_ICON
private val customData = HashMap<String, String>()
var notes = ""
@@ -58,28 +48,28 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var defaultAutoTypeSequence = ""
var enableAutoType: Boolean? = null
var enableSearching: Boolean? = null
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
var lastTopVisibleEntry: UUID = PwDatabase.UUID_ZERO
override var expires: Boolean = false
override val type: Type
get() = Type.GROUP
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
override fun initNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
override fun copyNodeId(nodeId: PwNodeId<UUID>): PwNodeId<UUID> {
return PwNodeIdUUID(nodeId.id)
}
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
iconCustom = parcel.readParcelable(PwIconCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong()
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
// TODO customData = ParcelableUtil.readStringParcelableMap(in);
locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged
// TODO customData = MemoryUtil.readStringParcelableMap(in);
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
@@ -90,11 +80,11 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
lastTopVisibleEntry = parcel.readSerializable() as UUID
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
override fun readParentParcelable(parcel: Parcel): PwGroupV4? {
return parcel.readParcelable(PwGroupV4::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
override fun writeParentParcelable(parent: PwGroupV4?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
@@ -103,7 +93,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount)
dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
// TODO MemoryUtil.writeStringParcelableMap(dest, customData);
dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte())
dest.writeString(defaultAutoTypeSequence)
@@ -112,11 +102,11 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
dest.writeSerializable(lastTopVisibleEntry)
}
fun updateWith(source: GroupKDBX) {
fun updateWith(source: PwGroupV4) {
super.updateWith(source)
iconCustom = IconImageCustom(source.iconCustom)
iconCustom = PwIconCustom(source.iconCustom)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
locationChanged = PwDate(source.locationChanged)
// Add all custom elements in map
customData.clear()
for ((key, value) in source.customData) {
@@ -132,10 +122,10 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var usageCount: Long = 0
override var locationChanged = DateInstant()
override var locationChanged = PwDate()
override fun afterAssignNewParent() {
locationChanged = DateInstant()
locationChanged = PwDate()
}
override fun putCustomData(key: String, value: String) {
@@ -153,12 +143,12 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
companion object {
@JvmField
val CREATOR: Parcelable.Creator<GroupKDBX> = object : Parcelable.Creator<GroupKDBX> {
override fun createFromParcel(parcel: Parcel): GroupKDBX {
return GroupKDBX(parcel)
val CREATOR: Parcelable.Creator<PwGroupV4> = object : Parcelable.Creator<PwGroupV4> {
override fun createFromParcel(parcel: Parcel): PwGroupV4 {
return PwGroupV4(parcel)
}
override fun newArray(size: Int): Array<GroupKDBX?> {
override fun newArray(size: Int): Array<PwGroupV4?> {
return arrayOfNulls(size)
}
}

View File

@@ -17,11 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
package com.kunzisoft.keepass.database.element
import android.os.Parcelable
abstract class IconImage protected constructor() : Parcelable {
abstract class PwIcon protected constructor() : Parcelable {
abstract val iconId: Int
abstract val isUnknown: Boolean

View File

@@ -17,15 +17,14 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.UUID
class IconImageCustom : IconImage {
class PwIconCustom : PwIcon {
val uuid: UUID
@Transient
@@ -41,7 +40,7 @@ class IconImageCustom : IconImage {
this.imageData = ByteArray(0)
}
constructor(icon: IconImageCustom) : super() {
constructor(icon: PwIconCustom) : super() {
uuid = icon.uuid
imageData = icon.imageData
}
@@ -69,7 +68,7 @@ class IconImageCustom : IconImage {
return true
if (other == null)
return false
if (other !is IconImageCustom)
if (other !is PwIconCustom)
return false
return uuid == other.uuid
}
@@ -84,15 +83,15 @@ class IconImageCustom : IconImage {
get() = false
companion object {
val UNKNOWN_ICON = IconImageCustom(DatabaseVersioned.UUID_ZERO, ByteArray(0))
val UNKNOWN_ICON = PwIconCustom(PwDatabase.UUID_ZERO, ByteArray(0))
@JvmField
val CREATOR: Parcelable.Creator<IconImageCustom> = object : Parcelable.Creator<IconImageCustom> {
override fun createFromParcel(parcel: Parcel): IconImageCustom {
return IconImageCustom(parcel)
val CREATOR: Parcelable.Creator<PwIconCustom> = object : Parcelable.Creator<PwIconCustom> {
override fun createFromParcel(parcel: Parcel): PwIconCustom {
return PwIconCustom(parcel)
}
override fun newArray(size: Int): Array<IconImageCustom?> {
override fun newArray(size: Int): Array<PwIconCustom?> {
return arrayOfNulls(size)
}
}

View File

@@ -17,61 +17,61 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
package com.kunzisoft.keepass.database.element
import org.apache.commons.collections.map.AbstractReferenceMap
import org.apache.commons.collections.map.ReferenceMap
import java.util.UUID
class IconImageFactory {
class PwIconFactory {
/** customIconMap
* Cache for icon drawable.
* Keys: Integer, Values: IconImageStandard
* Keys: Integer, Values: PwIconStandard
*/
private val cache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
/** standardIconMap
* Cache for icon drawable.
* Keys: UUID, Values: IconImageCustom
* Keys: UUID, Values: PwIconCustom
*/
private val customCache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
val unknownIcon: IconImageStandard
get() = getIcon(IconImage.UNKNOWN_ID)
val unknownIcon: PwIconStandard
get() = getIcon(PwIcon.UNKNOWN_ID)
val keyIcon: IconImageStandard
get() = getIcon(IconImageStandard.KEY)
val keyIcon: PwIconStandard
get() = getIcon(PwIconStandard.KEY)
val trashIcon: IconImageStandard
get() = getIcon(IconImageStandard.TRASH)
val trashIcon: PwIconStandard
get() = getIcon(PwIconStandard.TRASH)
val folderIcon: IconImageStandard
get() = getIcon(IconImageStandard.FOLDER)
val folderIcon: PwIconStandard
get() = getIcon(PwIconStandard.FOLDER)
fun getIcon(iconId: Int): IconImageStandard {
var icon: IconImageStandard? = cache[iconId] as IconImageStandard?
fun getIcon(iconId: Int): PwIconStandard {
var icon: PwIconStandard? = cache[iconId] as PwIconStandard?
if (icon == null) {
icon = IconImageStandard(iconId)
icon = PwIconStandard(iconId)
cache[iconId] = icon
}
return icon
}
fun getIcon(iconUuid: UUID): IconImageCustom {
var icon: IconImageCustom? = customCache[iconUuid] as IconImageCustom?
fun getIcon(iconUuid: UUID): PwIconCustom {
var icon: PwIconCustom? = customCache[iconUuid] as PwIconCustom?
if (icon == null) {
icon = IconImageCustom(iconUuid)
icon = PwIconCustom(iconUuid)
customCache[iconUuid] = icon
}
return icon
}
fun put(icon: IconImageCustom) {
fun put(icon: PwIconCustom) {
customCache[icon.uuid] = icon
}
}

View File

@@ -17,12 +17,12 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
class IconImageStandard : IconImage {
class PwIconStandard : PwIcon {
constructor() {
this.iconId = KEY
@@ -32,7 +32,7 @@ class IconImageStandard : IconImage {
this.iconId = iconId
}
constructor(icon: IconImageStandard) {
constructor(icon: PwIconStandard) {
this.iconId = icon.iconId
}
@@ -56,7 +56,7 @@ class IconImageStandard : IconImage {
return true
if (other == null)
return false
if (other !is IconImageStandard) {
if (other !is PwIconStandard) {
return false
}
return iconId == other.iconId
@@ -77,12 +77,12 @@ class IconImageStandard : IconImage {
const val FOLDER = 48
@JvmField
val CREATOR: Parcelable.Creator<IconImageStandard> = object : Parcelable.Creator<IconImageStandard> {
override fun createFromParcel(parcel: Parcel): IconImageStandard {
return IconImageStandard(parcel)
val CREATOR: Parcelable.Creator<PwIconStandard> = object : Parcelable.Creator<PwIconStandard> {
override fun createFromParcel(parcel: Parcel): PwIconStandard {
return PwIconStandard(parcel)
}
override fun newArray(size: Int): Array<IconImageStandard?> {
override fun newArray(size: Int): Array<PwIconStandard?> {
return arrayOfNulls(size)
}
}

View File

@@ -18,24 +18,18 @@
*
*
*/
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import org.joda.time.LocalDateTime
/**
* Abstract class who manage Groups and Entries
*/
abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, Entry>, Entry : EntryVersionedInterface<Parent>>
: NodeVersionedInterface<Parent>, NodeTimeInterface, Parcelable {
abstract class PwNode<IdType, Parent : PwGroupInterface<Parent, Entry>, Entry : PwEntryInterface<Parent>> : PwNodeInterface<Parent>, Parcelable {
var nodeId: NodeId<IdType> = this.initNodeId()
var nodeId: PwNodeId<IdType> = this.initNodeId()
val id: IdType
get() = nodeId.id
@@ -43,13 +37,13 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
protected constructor()
protected constructor(parcel: Parcel) {
this.nodeId = parcel.readParcelable(NodeId::class.java.classLoader) ?: nodeId
this.nodeId = parcel.readParcelable(PwNodeId::class.java.classLoader) ?: nodeId
this.parent = this.readParentParcelable(parcel)
this.icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
this.creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime
this.lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastModificationTime
this.lastAccessTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastAccessTime
this.expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
this.icon = parcel.readParcelable(PwIcon::class.java.classLoader) ?: icon
this.creationTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: creationTime
this.lastModificationTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: lastModificationTime
this.lastAccessTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: lastAccessTime
this.expiryTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: expiryTime
this.expires = parcel.readByte().toInt() != 0
}
@@ -68,33 +62,33 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
return 0
}
protected fun updateWith(source: NodeVersioned<IdType, Parent, Entry>) {
protected fun updateWith(source: PwNode<IdType, Parent, Entry>) {
this.nodeId = copyNodeId(source.nodeId)
this.parent = source.parent
this.icon = source.icon
this.creationTime = DateInstant(source.creationTime)
this.lastModificationTime = DateInstant(source.lastModificationTime)
this.lastAccessTime = DateInstant(source.lastAccessTime)
this.expiryTime = DateInstant(source.expiryTime)
this.creationTime = PwDate(source.creationTime)
this.lastModificationTime = PwDate(source.lastModificationTime)
this.lastAccessTime = PwDate(source.lastAccessTime)
this.expiryTime = PwDate(source.expiryTime)
this.expires = source.expires
}
protected abstract fun initNodeId(): NodeId<IdType>
protected abstract fun copyNodeId(nodeId: NodeId<IdType>): NodeId<IdType>
protected abstract fun initNodeId(): PwNodeId<IdType>
protected abstract fun copyNodeId(nodeId: PwNodeId<IdType>): PwNodeId<IdType>
protected abstract fun readParentParcelable(parcel: Parcel): Parent?
protected abstract fun writeParentParcelable(parent: Parent?, parcel: Parcel, flags: Int)
final override var parent: Parent? = null
override var icon: IconImage = IconImageStandard()
override var icon: PwIcon = PwIconStandard()
final override var creationTime: DateInstant = DateInstant()
final override var creationTime: PwDate = PwDate()
final override var lastModificationTime: DateInstant = DateInstant()
final override var lastModificationTime: PwDate = PwDate()
final override var lastAccessTime: DateInstant = DateInstant()
final override var lastAccessTime: PwDate = PwDate()
final override var expiryTime: DateInstant = DateInstant.NEVER_EXPIRE
final override var expiryTime: PwDate = PwDate()
final override val isCurrentlyExpires: Boolean
get() = expires
@@ -123,7 +117,7 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
}
override fun touch(modified: Boolean, touchParents: Boolean) {
val now = DateInstant()
val now = PwDate()
lastAccessTime = now
if (modified) {
@@ -140,7 +134,7 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
return true
if (other == null)
return false
if (other !is NodeVersioned<*, *, *>) {
if (other !is PwNode<*, *, *>) {
return false
}
return type == other.type && nodeId == other.nodeId

View File

@@ -17,12 +17,12 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
abstract class NodeId<Id> : Parcelable {
abstract class PwNodeId<Id> : Parcelable {
abstract val id: Id
@@ -34,7 +34,7 @@ abstract class NodeId<Id> : Parcelable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is NodeId<*>) return false
if (other !is PwNodeId<*>) return false
if (id != other.id) return false

View File

@@ -17,19 +17,19 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import java.util.Random
class NodeIdInt : NodeId<Int> {
class PwNodeIdInt : PwNodeId<Int> {
override var id: Int = -1
private set
constructor(source: NodeIdInt) : this(source.id)
constructor(source: PwNodeIdInt) : this(source.id)
@JvmOverloads
constructor(groupId: Int = Random().nextInt()) : super() {
@@ -50,7 +50,7 @@ class NodeIdInt : NodeId<Int> {
return true
if (other == null)
return false
if (other !is NodeIdInt) {
if (other !is PwNodeIdInt) {
return false
}
return id == other.id
@@ -66,12 +66,12 @@ class NodeIdInt : NodeId<Int> {
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdInt> = object : Parcelable.Creator<NodeIdInt> {
override fun createFromParcel(parcel: Parcel): NodeIdInt {
return NodeIdInt(parcel)
val CREATOR: Parcelable.Creator<PwNodeIdInt> = object : Parcelable.Creator<PwNodeIdInt> {
override fun createFromParcel(parcel: Parcel): PwNodeIdInt {
return PwNodeIdInt(parcel)
}
override fun newArray(size: Int): Array<NodeIdInt?> {
override fun newArray(size: Int): Array<PwNodeIdInt?> {
return arrayOfNulls(size)
}
}

View File

@@ -17,19 +17,19 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import java.util.UUID
class NodeIdUUID : NodeId<UUID> {
class PwNodeIdUUID : PwNodeId<UUID> {
override var id: UUID = UUID.randomUUID()
private set
constructor(source: NodeIdUUID) : this(source.id)
constructor(source: PwNodeIdUUID) : this(source.id)
@JvmOverloads
constructor(uuid: UUID = UUID.randomUUID()) : super() {
@@ -50,7 +50,7 @@ class NodeIdUUID : NodeId<UUID> {
return true
if (other == null)
return false
if (other !is NodeIdUUID) {
if (other !is PwNodeIdUUID) {
return false
}
return this.id == other.id
@@ -66,12 +66,12 @@ class NodeIdUUID : NodeId<UUID> {
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdUUID> = object : Parcelable.Creator<NodeIdUUID> {
override fun createFromParcel(parcel: Parcel): NodeIdUUID {
return NodeIdUUID(parcel)
val CREATOR: Parcelable.Creator<PwNodeIdUUID> = object : Parcelable.Creator<PwNodeIdUUID> {
override fun createFromParcel(parcel: Parcel): PwNodeIdUUID {
return PwNodeIdUUID(parcel)
}
override fun newArray(size: Int): Array<NodeIdUUID?> {
override fun newArray(size: Int): Array<PwNodeIdUUID?> {
return arrayOfNulls(size)
}
}

View File

@@ -1,16 +1,15 @@
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImage
interface NodeVersionedInterface<ParentGroup> : NodeTimeInterface, Parcelable {
interface PwNodeInterface<ParentGroup> : NodeTimeInterface, Parcelable {
var title: String
/**
* @return Visual icon
*/
var icon: IconImage
var icon: PwIcon
/**
* @return Type of Node

View File

@@ -1,19 +1,18 @@
package com.kunzisoft.keepass.database.element.node
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.DateInstant
import org.joda.time.LocalDateTime
interface NodeKDBInterface : NodeTimeInterface {
interface PwNodeV3Interface : NodeTimeInterface {
override var expires: Boolean
// If expireDate is before NEVER_EXPIRE date less 1 month (to be sure)
// it is not expires
get() = LocalDateTime(expiryTime.date)
.isBefore(LocalDateTime.fromDateFields(DateInstant.NEVER_EXPIRE.date)
.isBefore(LocalDateTime.fromDateFields(PwDate.NEVER_EXPIRE.date)
.minusMonths(1))
set(value) {
if (!value)
expiryTime = DateInstant.NEVER_EXPIRE
expiryTime = PwDate.NEVER_EXPIRE
}
}

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