Add room and fully manage database file history by SQL

This commit is contained in:
J-Jamet
2019-08-25 12:04:10 +02:00
parent 846b5fa449
commit 33767c2bf9
18 changed files with 316 additions and 700 deletions

View File

@@ -81,6 +81,7 @@ android {
def supportVersion = "27.1.1"
def spongycastleVersion = "1.58.0.0"
def room_version = "1.1.1"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -90,6 +91,10 @@ dependencies {
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Expandable view

View File

@@ -53,10 +53,7 @@ import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.fileselect.DeleteFileHistoryAsyncTask
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
import com.kunzisoft.keepass.fileselect.OpenFileHistoryAsyncTask
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.fileselect.*
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -165,8 +162,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
})
// Construct adapter with listeners
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity,
mFileDatabaseHistory?.databaseUriList ?: ArrayList())
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.setOnItemClickListener(this)
mAdapterDatabaseHistory?.setFileSelectClearListener(this)
mAdapterDatabaseHistory?.setFileInformationShowListener(this)
@@ -230,8 +226,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
if (createButtonView != null && createButtonView!!.visibility == View.VISIBLE
&& mFileDatabaseHistory != null
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createButtonView!!,
{
createNewFile()
@@ -264,7 +261,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
Log.e(TAG, error, e)
}
private fun launchPasswordActivity(fileName: String, keyFile: String) {
private fun launchPasswordActivity(fileName: String, keyFile: String?) {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
try {
@@ -327,8 +324,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume()
updateExternalStorageWarning()
updateFileListVisibility()
mAdapterDatabaseHistory!!.notifyDataSetChanged()
// Construct adapter with listeners
mFileDatabaseHistory?.getAll { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -404,12 +408,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
override fun onFileItemOpenListener(itemPosition: Int) {
OpenFileHistoryAsyncTask({ fileName, keyFile ->
if (fileName != null && keyFile != null)
launchPasswordActivity(fileName, keyFile)
updateFileListVisibility()
}, mFileDatabaseHistory).execute(itemPosition)
override fun onFileItemOpenListener(fileHistoryEntity: DatabaseFileHistoryEntity) {
launchPasswordActivity(fileHistoryEntity.databaseUri, fileHistoryEntity.keyFileUri)
updateFileListVisibility()
}
override fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel) {
@@ -417,13 +418,16 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
override fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean {
DeleteFileHistoryAsyncTask({
fileDatabaseModel.fileUri?.let {
mFileDatabaseHistory?.deleteDatabaseUri(it)
fileDatabaseModel.databaseFileUri?.let {
mFileDatabaseHistory?.deleteDatabaseUri(it) { fileHistoryDeleted ->
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
}
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}, mFileDatabaseHistory, mAdapterDatabaseHistory).execute(fileDatabaseModel)
}
return true
}

View File

@@ -555,11 +555,12 @@ class PasswordActivity : StylishActivity(),
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String,
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName)
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile)
if (keyFile != null)
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile)
intentBuildLauncher.invoke(intent)
}
@@ -589,7 +590,7 @@ class PasswordActivity : StylishActivity(),
fun launch(
activity: Activity,
fileName: String,
keyFile: String) {
keyFile: String?) {
verifyFileNameUriFromLaunch(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
activity.startActivity(intent)
@@ -606,7 +607,7 @@ class PasswordActivity : StylishActivity(),
fun launchForKeyboardResult(
activity: Activity,
fileName: String,
keyFile: String) {
keyFile: String?) {
verifyFileNameUriFromLaunch(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
@@ -625,7 +626,7 @@ class PasswordActivity : StylishActivity(),
fun launchForAutofillResult(
activity: Activity,
fileName: String,
keyFile: String,
keyFile: String?,
assistStructure: AssistStructure?) {
verifyFileNameUriFromLaunch(fileName)

View File

@@ -50,7 +50,7 @@ class FileInformationDialogFragment : DialogFragment() {
arguments?.apply {
if (containsKey(FILE_SELECT_BEEN_ARG)) {
(getSerializable(FILE_SELECT_BEEN_ARG) as FileDatabaseModel?)?.let { fileDatabaseModel ->
fileDatabaseModel.fileUri?.let { fileUri ->
fileDatabaseModel.databaseFileUri?.let { fileUri ->
filePathView.text = Uri.decode(fileUri.toString())
}
fileNameView.text = fileDatabaseModel.fileName

View File

@@ -6,15 +6,15 @@ import android.net.Uri
import android.os.AsyncTask
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.fileselect.FileDatabaseHistory
import com.kunzisoft.keepass.utils.UriUtil
import java.io.File
import java.lang.ref.WeakReference
class UriIntentInitTask(private val weakContext: WeakReference<Context>,
private val uriIntentInitTaskCallback: UriIntentInitTaskCallback,
private val isKeyFileNeeded: Boolean)
private val uriIntentInitTaskCallback: UriIntentInitTaskCallback,
private val isKeyFileNeeded: Boolean)
: AsyncTask<Intent, Void, Int>() {
private var databaseUri: Uri? = null
@@ -46,13 +46,9 @@ class UriIntentInitTask(private val weakContext: WeakReference<Context>,
return R.string.file_not_found
}
if (keyFileUri == null) {
keyFileUri = getKeyFileUri(databaseUri)
}
return null
} else if (incoming.scheme == "content") {
if (keyFileUri == null) {
keyFileUri = getKeyFileUri(databaseUri)
}
return null
} else {
return R.string.error_can_not_handle_uri
}
@@ -61,22 +57,22 @@ class UriIntentInitTask(private val weakContext: WeakReference<Context>,
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
if (keyFileUri == null || keyFileUri!!.toString().isEmpty()) {
keyFileUri = getKeyFileUri(databaseUri)
}
return null
}
return null
}
public override fun onPostExecute(result: Int?) {
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result)
}
private fun getKeyFileUri(databaseUri: Uri?): Uri? {
return if (isKeyFileNeeded) {
FileDatabaseHistory.getInstance(weakContext).getKeyFileUriByDatabaseUri(databaseUri!!)
if (isKeyFileNeeded && (keyFileUri == null || keyFileUri!!.toString().isEmpty())) {
// Retrieve KeyFile in a thread if needed
databaseUri?.let { databaseUriNotNull ->
FileDatabaseHistory.getInstance(weakContext)
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
uriIntentInitTaskCallback.onPostInitTask(databaseUri, it, result)
}
}
} else {
null
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result)
}
}

View File

@@ -28,10 +28,11 @@ import android.view.*
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.DatabaseFileHistoryEntity
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
import com.kunzisoft.keepass.settings.PreferencesUtil
class FileDatabaseHistoryAdapter(private val context: Context, private val listFiles: List<String>)
class FileDatabaseHistoryAdapter(private val context: Context)
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -39,6 +40,8 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
private var fileSelectClearListener: FileSelectClearListener? = null
private var fileInformationShowListener: FileInformationShowListener? = null
private val listDatabaseFiles = ArrayList<DatabaseFileHistoryEntity>()
@ColorInt
private val defaultColor: Int
@ColorInt
@@ -60,25 +63,36 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
}
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position])
val fileHistoryEntity = listDatabaseFiles[position]
val fileDatabaseInfo = FileDatabaseModel(context, fileHistoryEntity.databaseUri)
// Context menu creation
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel))
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseInfo))
// Click item to open file
if (fileItemOpenListener != null)
holder.fileContainer.setOnClickListener(FileItemClickListener(position))
holder.fileContainer.setOnClickListener(FileItemClickListener(fileHistoryEntity))
// Assign file name
if (PreferencesUtil.isFullFilePathEnable(context))
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
holder.fileName.text = Uri.decode(fileDatabaseInfo.databaseFileUri.toString())
else
holder.fileName.text = fileDatabaseModel.fileName
holder.fileName.text = fileDatabaseInfo.fileName
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
// Click on information
if (fileInformationShowListener != null)
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel))
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseInfo))
}
override fun getItemCount(): Int {
return listFiles.size
return listDatabaseFiles.size
}
fun addDatabaseFileHistoryList(listDatabaseFileHistoryToAdd: List<DatabaseFileHistoryEntity>) {
listDatabaseFiles.clear()
listDatabaseFiles.addAll(listDatabaseFileHistoryToAdd)
}
fun deleteDatabaseFileHistory(databaseFileHistoryToDelete: DatabaseFileHistoryEntity) {
listDatabaseFiles.remove(databaseFileHistoryToDelete)
}
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) {
@@ -94,7 +108,7 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
}
interface FileItemOpenListener {
fun onFileItemOpenListener(itemPosition: Int)
fun onFileItemOpenListener(fileHistoryEntity: DatabaseFileHistoryEntity)
}
interface FileSelectClearListener {
@@ -105,10 +119,10 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel)
}
private inner class FileItemClickListener(private val position: Int) : View.OnClickListener {
private inner class FileItemClickListener(private val fileHistoryEntity: DatabaseFileHistoryEntity) : View.OnClickListener {
override fun onClick(v: View) {
fileItemOpenListener?.onFileItemOpenListener(position)
fileItemOpenListener?.onFileItemOpenListener(fileHistoryEntity)
}
}

View File

@@ -27,7 +27,7 @@ import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.fileselect.FileDatabaseHistory
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.FileNotFoundException

View File

@@ -0,0 +1,32 @@
package com.kunzisoft.keepass.fileselect
import android.arch.persistence.db.SupportSQLiteQuery
import android.arch.persistence.room.*
@Dao
interface DatabaseFileHistoryDao {
@Query("SELECT * FROM database_file_history ORDER BY updated DESC")
fun getAll(): List<DatabaseFileHistoryEntity>
@Query("SELECT * FROM database_file_history WHERE database_uri = :databaseUriString")
fun getByDatabaseUri(databaseUriString: String): DatabaseFileHistoryEntity?
@Insert
fun add(vararg databaseFileHistory: DatabaseFileHistoryEntity)
@Update
fun update(vararg databaseFileHistory: DatabaseFileHistoryEntity)
@Delete
fun delete(databaseFileHistory: DatabaseFileHistoryEntity): Int
/* TODO Replace (Insert not yet supported)
@Query("REPLACE INTO database_file_history(keyfile_uri) VALUES(null)")
fun deleteAllKeyFiles()
*/
@RawQuery
fun deleteAllKeyFiles(query: SupportSQLiteQuery): Boolean
@Query("DELETE FROM database_file_history")
fun deleteAll()
}

View File

@@ -0,0 +1,20 @@
package com.kunzisoft.keepass.fileselect
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
@Database(entities = [DatabaseFileHistoryEntity::class], version = 1)
abstract class DatabaseFileHistoryDatabase : RoomDatabase() {
abstract fun databaseFileHistoryDao(): DatabaseFileHistoryDao
companion object {
fun getDatabase(applicationContext: Context): DatabaseFileHistoryDatabase {
return Room.databaseBuilder(
applicationContext,
DatabaseFileHistoryDatabase::class.java, "database-name"
).build()
}
}
}

View File

@@ -0,0 +1,36 @@
package com.kunzisoft.keepass.fileselect
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "database_file_history")
data class DatabaseFileHistoryEntity(
@PrimaryKey
@ColumnInfo(name = "database_uri")
val databaseUri: String,
@ColumnInfo(name = "database_alias")
val databaseAlias: String,
@ColumnInfo(name = "keyfile_uri")
var keyFileUri: String?,
@ColumnInfo(name = "updated")
val updated: Long
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DatabaseFileHistoryEntity
if (databaseUri != other.databaseUri) return false
return true
}
override fun hashCode(): Int {
return databaseUri.hashCode()
}
}

View File

@@ -1,45 +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.fileselect
import android.os.AsyncTask
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
class DeleteFileHistoryAsyncTask(private val afterDeleteFileHistoryListener: (() -> Unit)?,
private val fileHistory: FileDatabaseHistory?,
private val adapter: FileDatabaseHistoryAdapter?)
: AsyncTask<FileDatabaseModel, Void, Void>() {
override fun doInBackground(vararg args: FileDatabaseModel): Void? {
args[0].fileUri?.let {
fileHistory?.deleteDatabaseUri(it)
}
return null
}
override fun onPostExecute(v: Void?) {
adapter?.notifyDataSetChanged()
if (adapter == null || adapter.itemCount == 0) {
afterDeleteFileHistoryListener?.invoke()
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* 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.fileselect
import android.arch.persistence.db.SimpleSQLiteQuery
import android.content.Context
import android.net.Uri
import android.os.AsyncTask
import com.kunzisoft.keepass.utils.SingletonHolderParameter
import java.lang.ref.WeakReference
class FileDatabaseHistory(val context: WeakReference<Context>) {
private val databaseFileHistoryDao =
DatabaseFileHistoryDatabase
.getDatabase(context.get()!!)
.databaseFileHistoryDao()
fun getAll(fileHistoryResultListener: (fileHistoryResult: List<DatabaseFileHistoryEntity>?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.getAll()
},
{
fileHistoryResultListener.invoke(it)
}
).execute()
}
fun addDatabaseUri(databaseUri: Uri, keyFileUri: Uri? = null) {
ActionFileHistoryAsyncTask(
{
val newDatabaseFileHistory = DatabaseFileHistoryEntity(
databaseUri.toString(),
"",
keyFileUri?.toString(),
System.currentTimeMillis()
)
// Update values if history element not yet in the database
if (databaseFileHistoryDao.getByDatabaseUri(newDatabaseFileHistory.databaseUri) == null) {
databaseFileHistoryDao.add(newDatabaseFileHistory)
} else {
databaseFileHistoryDao.update(newDatabaseFileHistory)
}
}
).execute()
}
fun getKeyFileUriByDatabaseUri(databaseUri: Uri,
keyFileUriResultListener: (Uri?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
},
{
it?.let { fileHistoryEntity ->
fileHistoryEntity.keyFileUri?.let { keyFileUri ->
keyFileUriResultListener.invoke(Uri.parse(keyFileUri))
}
} ?: keyFileUriResultListener.invoke(null)
}
).execute()
}
fun deleteDatabaseUri(databaseUri: Uri,
fileHistoryDeletedResult: (DatabaseFileHistoryEntity?) -> Unit) {
val databaseFileHistoryDeleted = DatabaseFileHistoryEntity(
databaseUri.toString(),
"",
null,
0)
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.delete(databaseFileHistoryDeleted)
},
{
if (it != null && it > 0)
fileHistoryDeletedResult.invoke(databaseFileHistoryDeleted)
else
fileHistoryDeletedResult.invoke(null)
}
).execute()
}
fun deleteAllKeyFiles() {
// TODO replace for unsupported query databaseFileHistoryDao.deleteAllKeyFiles()
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao
.deleteAllKeyFiles(SimpleSQLiteQuery("REPLACE INTO database_file_history(keyfile_uri) VALUES(null)"))
}
).execute()
}
fun deleteAll() {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.deleteAll()
}
).execute()
}
/**
* Private class to invoke each method in a separate thread
*/
private class ActionFileHistoryAsyncTask<T>(
private val action: () -> T ,
private val afterActionFileHistoryListener: ((fileHistoryResult: T?) -> Unit)? = null
) : AsyncTask<Void, Void, T>() {
override fun doInBackground(vararg args: Void?): T? {
return action.invoke()
}
override fun onPostExecute(result: T?) {
afterActionFileHistoryListener?.invoke(result)
}
}
companion object : SingletonHolderParameter<FileDatabaseHistory, WeakReference<Context>>(::FileDatabaseHistory)
}

View File

@@ -30,26 +30,26 @@ import java.util.Date
class FileDatabaseModel(context: Context, pathFile: String) : Serializable {
var fileName: String? = ""
var fileUri: Uri? = null
var databaseFileUri: Uri? = null
var lastModification = Date()
var size: Long = 0L
init {
fileUri = Uri.parse(pathFile)
if (EXTERNAL_STORAGE_AUTHORITY == fileUri!!.authority) {
val file = DocumentFile.fromSingleUri(context, fileUri)
databaseFileUri = Uri.parse(pathFile)
if (EXTERNAL_STORAGE_AUTHORITY == databaseFileUri!!.authority) {
val file = DocumentFile.fromSingleUri(context, databaseFileUri)
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
} else {
val file = File(fileUri!!.path!!)
val file = File(databaseFileUri!!.path!!)
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
}
if (fileName == null || fileName!!.isEmpty()) {
fileName = fileUri!!.path
fileName = databaseFileUri!!.path
}
}

View File

@@ -1,44 +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.fileselect
import android.os.AsyncTask
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
class OpenFileHistoryAsyncTask(private val afterOpenFileHistoryListener: ((fileName: String?, keyFile: String?) -> Unit)?,
private val fileHistory: FileDatabaseHistory?)
: AsyncTask<Int, Void, Void>() {
private var fileName: String? = null
private var keyFile: String? = null
override fun doInBackground(vararg args: Int?): Void? {
args[0]?.let {
fileName = fileHistory?.getDatabaseAt(it)
keyFile = fileHistory?.getKeyFileAt(it)
}
return null
}
override fun onPostExecute(v: Void?) {
afterOpenFileHistoryListener?.invoke(fileName, keyFile)
}
}

View File

@@ -1,227 +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.fileselect.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import java.io.File;
import java.io.FileFilter;
public class FileDatabaseHelper {
private static final String TAG = FileDatabaseHelper.class.getName();
static final String LAST_FILENAME = "lastFile";
static final String LAST_KEYFILE = "lastKey";
public static final String DATABASE_NAME = "keepassdroid"; // TODO Change db name
static final String FILE_TABLE = "files";
static final int DATABASE_VERSION = 1;
public static final int MAX_FILES = 5;
public static final String KEY_FILE_ID = "_id";
public static final String KEY_FILE_FILENAME = "fileName";
public static final String KEY_FILE_KEYFILE = "keyFile";
public static final String KEY_FILE_UPDATED = "updated";
static final String DATABASE_CREATE =
"create table " + FILE_TABLE + " ( " + KEY_FILE_ID + " integer primary key autoincrement, "
+ KEY_FILE_FILENAME + " text not null, " + KEY_FILE_KEYFILE + " text, "
+ KEY_FILE_UPDATED + " integer not null);";
private final Context mCtx;
private SQLiteDatabase mDb;
public FileDatabaseHelper(Context ctx) {
mCtx = ctx;
}
public FileDatabaseHelper open() throws SQLException {
mDb = new FileDatabaseHistoryHelper(mCtx).getWritableDatabase();
return this;
}
public boolean isOpen() {
return mDb.isOpen();
}
public void close() {
mDb.close();
}
public long createFile(String fileName, String keyFile) {
// Check to see if this filename is already used
Cursor cursor;
try {
cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_ID},
KEY_FILE_FILENAME + "=?", new String[] {fileName}, null, null, null, null);
} catch (Exception e ) {
return -1;
}
long result;
// If there is an existing entry update it with the new key file
if ( cursor.getCount() > 0 ) {
cursor.moveToFirst();
long id = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_ID));
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_KEYFILE, keyFile);
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
result = mDb.update(FILE_TABLE, vals, KEY_FILE_ID + " = " + id, null);
// Otherwise add the new entry
} else {
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_FILENAME, fileName);
vals.put(KEY_FILE_KEYFILE, keyFile);
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
result = mDb.insert(FILE_TABLE, null, vals);
}
// Delete all but the last five records
try {
deleteAllBut(MAX_FILES);
} catch (Exception e) {
e.printStackTrace();
}
cursor.close();
return result;
}
private void deleteAllBut(int limit) {
Cursor cursor = mDb.query(FILE_TABLE, new String[] {KEY_FILE_UPDATED}, null, null, null, null, KEY_FILE_UPDATED);
if ( cursor.getCount() > limit ) {
cursor.moveToFirst();
long time = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_UPDATED));
mDb.execSQL("DELETE FROM " + FILE_TABLE + " WHERE " + KEY_FILE_UPDATED + "<" + time + ";");
}
cursor.close();
}
public void deleteAllKeys() {
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_KEYFILE, "");
mDb.update(FILE_TABLE, vals, null, null);
}
public void deleteFile(String filename) {
mDb.delete(FILE_TABLE, KEY_FILE_FILENAME + " = ?", new String[] {filename});
}
public Cursor fetchAllFiles() {
Cursor ret;
ret = mDb.query(FILE_TABLE, new String[] {KEY_FILE_ID, KEY_FILE_FILENAME, KEY_FILE_KEYFILE }, null, null, null, null, KEY_FILE_UPDATED + " DESC", Integer.toString(MAX_FILES));
return ret;
}
public Cursor fetchFile(long fileId) throws SQLException {
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_FILENAME, KEY_FILE_KEYFILE},
KEY_FILE_ID + "=" + fileId, null, null, null, null, null);
if ( cursor != null ) {
cursor.moveToFirst();
}
return cursor;
}
public String getFileByName(String name) {
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_KEYFILE},
KEY_FILE_FILENAME + "= ?", new String[] {name}, null, null, null, null);
if ( cursor == null ) {
return "";
}
String filename;
if ( cursor.moveToFirst() ) {
filename = cursor.getString(0);
} else {
// Cursor is empty
filename = "";
}
cursor.close();
return filename;
}
public boolean hasRecentFiles() {
Cursor cursor = fetchAllFiles();
boolean hasRecent = cursor.getCount() > 0;
cursor.close();
return hasRecent;
}
/**
* Deletes a database including its journal file and other auxiliary files
* that may have been created by the database engine.
*
* @param ctx Context to get database path
* @return True if the database was successfully deleted.
*/
public static boolean deleteDatabase(Context ctx) {
File file = ctx.getDatabasePath(DATABASE_NAME);
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
boolean deleted = false;
deleted |= file.delete();
deleted |= new File(file.getPath() + "-journal").delete();
deleted |= new File(file.getPath() + "-shm").delete();
deleted |= new File(file.getPath() + "-wal").delete();
File dir = file.getParentFile();
if (dir != null) {
final String prefix = file.getName() + "-mj";
final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().startsWith(prefix);
}
};
for (File masterJournal : dir.listFiles(filter)) {
deleted |= masterJournal.delete();
}
}
return deleted;
}
}

View File

@@ -1,258 +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.fileselect.database
import android.content.Context
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.preference.PreferenceManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.SingletonHolderParameter
import com.kunzisoft.keepass.utils.UriUtil
import java.lang.ref.WeakReference
import java.util.*
class FileDatabaseHistory private constructor(private val context: WeakReference<Context>) {
private val mDatabasesUriList = ArrayList<String>()
private val mKeyFilesUriList = ArrayList<String>()
private val mPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.get())
var isEnabled: Boolean = false
val databaseUriList: List<String>
get() {
init()
return mDatabasesUriList
}
private val onSharedPreferenceChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == context.get()?.getString(R.string.recentfile_key)) {
isEnabled = sharedPreferences.getBoolean(
context.get()?.getString(R.string.recentfile_key),
context.get()?.resources?.getBoolean(R.bool.recentfile_default) ?: isEnabled)
}
}
init {
mPreferences.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
context.get()?.resources?.let {
isEnabled = mPreferences.getBoolean(
it.getString(R.string.recentfile_key),
it.getBoolean(R.bool.recentfile_default))
}
mPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
}
private var init = false
@Synchronized
private fun init() {
if (!init) {
if (!upgradeFromSQL()) {
loadPrefs()
}
init = true
}
}
private fun upgradeFromSQL(): Boolean {
try {
// Check for a database to upgrade from
if (context.get()?.getDatabasePath(FileDatabaseHelper.DATABASE_NAME)?.exists() != true) {
return false
}
mDatabasesUriList.clear()
mKeyFilesUriList.clear()
val helper = FileDatabaseHelper(context.get())
helper.open()
val cursor = helper.fetchAllFiles()
val dbIndex = cursor.getColumnIndex(FileDatabaseHelper.KEY_FILE_FILENAME)
val keyIndex = cursor.getColumnIndex(FileDatabaseHelper.KEY_FILE_KEYFILE)
if (cursor.moveToFirst()) {
while (cursor.moveToNext()) {
mDatabasesUriList.add(cursor.getString(dbIndex))
mKeyFilesUriList.add(cursor.getString(keyIndex))
}
}
savePrefs()
cursor.close()
helper.close()
} catch (e: Exception) {
// If upgrading fails, we'll just give up on it.
}
try {
FileDatabaseHelper.deleteDatabase(context.get())
} catch (e: Exception) {
// If we fail to delete it, just move on
}
return true
}
@JvmOverloads
fun addDatabaseUri(databaseUri: Uri?, keyFileUri: Uri? = null) {
if (!isEnabled || databaseUri == null) return
init()
// Remove any existing instance of the same filename
deleteDatabaseUri(databaseUri, false)
mDatabasesUriList.add(0, databaseUri.toString())
val key = keyFileUri?.toString() ?: ""
mKeyFilesUriList.add(0, key)
trimLists()
savePrefs()
}
fun hasRecentFiles(): Boolean {
if (!isEnabled) return false
init()
return mDatabasesUriList.size > 0
}
fun getDatabaseAt(i: Int): String {
init()
return mDatabasesUriList[i]
}
fun getKeyFileAt(i: Int): String {
init()
return mKeyFilesUriList[i]
}
private fun loadPrefs() {
loadList(DB_KEY, mDatabasesUriList)
loadList(KEY_FILE_KEY, mKeyFilesUriList)
}
private fun savePrefs() {
saveList(DB_KEY, mDatabasesUriList)
saveList(KEY_FILE_KEY, mKeyFilesUriList)
}
private fun loadList(keyPrefix: String, list: MutableList<String>) {
val size = mPreferences.getInt(keyPrefix, 0)
list.clear()
for (i in 0 until size) {
mPreferences.getString(keyPrefix + "_" + i, "")?.let {
list.add(it)
}
}
}
private fun saveList(keyPrefix: String, list: List<String>) {
val edit = mPreferences.edit()
val size = list.size
edit.putInt(keyPrefix, size)
for (i in 0 until size) {
edit.putString(keyPrefix + "_" + i, list[i])
}
edit.apply()
}
@JvmOverloads
fun deleteDatabaseUri(uri: Uri, save: Boolean = true) {
init()
val uriName = uri.toString()
val fileName = uri.path
for (i in mDatabasesUriList.indices) {
val entry = mDatabasesUriList[i]
if (uriName == entry || fileName == entry) {
mDatabasesUriList.removeAt(i)
mKeyFilesUriList.removeAt(i)
break
}
}
if (save) {
savePrefs()
}
}
fun getKeyFileUriByDatabaseUri(uri: Uri): Uri? {
if (!isEnabled)
return null
init()
val size = mDatabasesUriList.size
for (i in 0 until size) {
if (uri == UriUtil.parseUriFile(mDatabasesUriList[i])) {
return UriUtil.parseUriFile(mKeyFilesUriList[i])
}
}
return null
}
fun deleteAll() {
init()
mDatabasesUriList.clear()
mKeyFilesUriList.clear()
savePrefs()
}
fun deleteAllKeys() {
init()
mKeyFilesUriList.clear()
val size = mDatabasesUriList.size
for (i in 0 until size) {
mKeyFilesUriList.add("")
}
savePrefs()
}
private fun trimLists() {
val size = mDatabasesUriList.size
for (i in FileDatabaseHelper.MAX_FILES until size) {
mDatabasesUriList.removeAt(i)
mKeyFilesUriList.removeAt(i)
}
}
companion object : SingletonHolderParameter<FileDatabaseHistory, WeakReference<Context>>(::FileDatabaseHistory) {
private const val DB_KEY = "recent_databases"
private const val KEY_FILE_KEY = "recent_keyfiles"
}
}

View File

@@ -1,58 +0,0 @@
package com.kunzisoft.keepass.fileselect.database;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
class FileDatabaseHistoryHelper extends SQLiteOpenHelper {
private final SharedPreferences settings;
FileDatabaseHistoryHelper(Context context) {
super(context, FileDatabaseHelper.DATABASE_NAME, null, FileDatabaseHelper.DATABASE_VERSION);
settings = context.getSharedPreferences("PasswordActivity", Context.MODE_PRIVATE);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(FileDatabaseHelper.DATABASE_CREATE);
// Migrate preference to database if it is set.
String lastFile = settings.getString(FileDatabaseHelper.LAST_FILENAME, "");
String lastKey = settings.getString(FileDatabaseHelper.LAST_KEYFILE,"");
if ( lastFile.length() > 0 ) {
ContentValues contentValues = new ContentValues();
contentValues.put(FileDatabaseHelper.KEY_FILE_FILENAME, lastFile);
contentValues.put(FileDatabaseHelper.KEY_FILE_UPDATED, System.currentTimeMillis());
if ( lastKey.length() > 0 ) {
contentValues.put(FileDatabaseHelper.KEY_FILE_KEYFILE, lastKey);
}
sqLiteDatabase.insert(FileDatabaseHelper.FILE_TABLE, null, contentValues);
// Clear old preferences
deletePrefs(settings);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Only one database version so far
}
private void deletePrefs(SharedPreferences prefs) {
// We won't worry too much if this fails
try {
SharedPreferences.Editor editor = prefs.edit();
editor.remove(FileDatabaseHelper.LAST_FILENAME);
editor.remove(FileDatabaseHelper.LAST_KEYFILE);
editor.apply();
} catch (Exception e) {
Log.e(FileDatabaseHelper.class.getName(), "Unable to delete database preference", e);
}
}
}

View File

@@ -47,7 +47,7 @@ import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.fileselect.FileDatabaseHistory
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper
import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager
import com.kunzisoft.keepass.icons.IconPackChooser
@@ -119,7 +119,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
val keyFile = findPreference(getString(R.string.keyfile_key))
keyFile.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) {
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAllKeys()
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAllKeyFiles()
}
true
}