mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add thread to load images
This commit is contained in:
@@ -126,6 +126,7 @@ class EntryActivity : LockingActivity() {
|
||||
historyView = findViewById(R.id.history_container)
|
||||
entryContentsView = findViewById(R.id.entry_contents)
|
||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||
entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey)
|
||||
entryProgress = findViewById(R.id.entry_progress)
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
// Build fragment to manage entry modification
|
||||
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
|
||||
if (entryEditFragment == null) {
|
||||
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
|
||||
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey)
|
||||
}
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrCha
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
@@ -86,6 +87,7 @@ class EntryEditFragment: StylishFragment() {
|
||||
|
||||
// Elements to modify the current entry
|
||||
private var mEntryInfo = EntryInfo()
|
||||
private var mBinaryCipherKey: Database.LoadedKey? = null
|
||||
private var mLastFocusedEditField: FocusedEditField? = null
|
||||
private var mExtraViewToRequestFocus: EditText? = null
|
||||
|
||||
@@ -127,6 +129,7 @@ class EntryEditFragment: StylishFragment() {
|
||||
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
|
||||
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
|
||||
attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey?
|
||||
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
|
||||
if (previousSize > 0 && newSize == 0) {
|
||||
attachmentsContainerView.collapse(true)
|
||||
@@ -528,6 +531,7 @@ class EntryEditFragment: StylishFragment() {
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
populateEntryWithViews()
|
||||
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
|
||||
outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey)
|
||||
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
@@ -535,12 +539,15 @@ class EntryEditFragment: StylishFragment() {
|
||||
|
||||
companion object {
|
||||
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
|
||||
const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY"
|
||||
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
|
||||
|
||||
fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
|
||||
fun getInstance(entryInfo: EntryInfo?,
|
||||
loadedKey: Database.LoadedKey?): EntryEditFragment {
|
||||
return EntryEditFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
|
||||
putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@ package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.igreenwood.loupe.Loupe
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import kotlinx.android.synthetic.main.activity_image_viewer.*
|
||||
|
||||
class ImageViewerActivity : LockingActivity() {
|
||||
@@ -38,13 +39,20 @@ class ImageViewerActivity : LockingActivity() {
|
||||
|
||||
setContentView(R.layout.activity_image_viewer)
|
||||
val imageView: ImageView = findViewById(R.id.image_viewer_image)
|
||||
val progressView: View = findViewById(R.id.image_viewer_progress)
|
||||
|
||||
try {
|
||||
progressView.visibility = View.VISIBLE
|
||||
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
|
||||
BitmapFactory.decodeStream(attachment.binaryAttachment.getUnGzipInputDataStream())?.let { imageBitmap ->
|
||||
imageView.setImageBitmap(imageBitmap)
|
||||
} ?: finish()
|
||||
Attachment.loadBitmap(attachment, Database.getInstance().loadedCipherKey) { bitmapLoaded ->
|
||||
if (bitmapLoaded == null) {
|
||||
finish()
|
||||
} else {
|
||||
progressView.visibility = View.GONE
|
||||
imageView.setImageBitmap(bitmapLoaded)
|
||||
}
|
||||
}
|
||||
} ?: finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to view the binary", e)
|
||||
finish()
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.text.format.Formatter
|
||||
import android.util.TypedValue
|
||||
@@ -33,6 +33,8 @@ import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.ImageViewerActivity
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
@@ -42,8 +44,12 @@ import com.kunzisoft.keepass.model.StreamDirection
|
||||
class EntryAttachmentsItemsAdapter(context: Context)
|
||||
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||
|
||||
var binaryCipherKey: Database.LoadedKey? = null
|
||||
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
||||
|
||||
// To manage image preview loading
|
||||
private val imageBinariesSet = HashMap<Int, Bitmap?>()
|
||||
|
||||
private var mTitleColor: Int
|
||||
|
||||
init {
|
||||
@@ -65,14 +71,27 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
||||
|
||||
holder.itemView.visibility = View.VISIBLE
|
||||
holder.binaryFileThumbnail.apply {
|
||||
BitmapFactory.decodeStream(entryAttachmentState.attachment.binaryAttachment.getUnGzipInputDataStream())?.let { imageBitmap ->
|
||||
setImageBitmap(imageBitmap)
|
||||
// Show the bitmap image if loaded
|
||||
if (imageBinariesSet.containsKey(position)) {
|
||||
if (imageBinariesSet[position] != null) {
|
||||
setImageBitmap(imageBinariesSet[position])
|
||||
}
|
||||
} else {
|
||||
imageBinariesSet[position] = null
|
||||
// Load the bitmap image
|
||||
Attachment.loadBitmap(entryAttachmentState.attachment, binaryCipherKey) { imageLoaded ->
|
||||
imageBinariesSet[position] = imageLoaded
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
setOnClickListener {
|
||||
ImageViewerActivity.getInstance(context, entryAttachmentState.attachment)
|
||||
}
|
||||
visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
visibility = View.GONE
|
||||
visibility = if (imageBinariesSet.containsKey(position)) {
|
||||
setImageBitmap(imageBinariesSet[position])
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
holder.binaryFileBroken.apply {
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
data class Attachment(var name: String,
|
||||
var binaryAttachment: BinaryAttachment) : Parcelable {
|
||||
@@ -65,5 +68,25 @@ data class Attachment(var name: String,
|
||||
override fun newArray(size: Int): Array<Attachment?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
fun loadBitmap(attachment: Attachment,
|
||||
binaryCipherKey: Database.LoadedKey?,
|
||||
actionOnFinish: (Bitmap?) -> Unit) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val asyncResult: Deferred<Bitmap?> = async {
|
||||
runCatching {
|
||||
binaryCipherKey?.let { binaryKey ->
|
||||
BitmapFactory.decodeStream(attachment.binaryAttachment
|
||||
.getUnGzipInputDataStream(binaryKey))
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
actionOnFinish(asyncResult.await())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,14 +346,14 @@ class Database {
|
||||
this.loaded = true
|
||||
}
|
||||
|
||||
class LoadedKey(val key: Key, val iv: IvParameterSpec) {
|
||||
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
|
||||
companion object {
|
||||
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
|
||||
|
||||
fun generateNewCipherKey(): LoadedKey {
|
||||
val iv = ByteArray(8)
|
||||
SecureRandom().nextBytes(iv)
|
||||
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), IvParameterSpec(iv))
|
||||
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
class BinaryAttachment : Parcelable {
|
||||
|
||||
@@ -101,7 +102,7 @@ class BinaryAttachment : Parcelable {
|
||||
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
|
||||
return when {
|
||||
file != null && file.length() > 0 -> {
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, cipherKey.iv)
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
|
||||
}
|
||||
else -> ByteArrayInputStream(ByteArray(0))
|
||||
@@ -112,7 +113,7 @@ class BinaryAttachment : Parcelable {
|
||||
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
|
||||
return when {
|
||||
file != null -> {
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, cipherKey.iv)
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
|
||||
}
|
||||
else -> throw IOException("Unable to write in an unknown file")
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
@@ -321,6 +322,10 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
* -------------
|
||||
*/
|
||||
|
||||
fun setAttachmentCipherKey(cipherKey: Database.LoadedKey?) {
|
||||
attachmentsAdapter.binaryCipherKey = cipherKey
|
||||
}
|
||||
|
||||
private fun showAttachments(show: Boolean) {
|
||||
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground">
|
||||
<ProgressBar
|
||||
android:id="@+id/image_viewer_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
<ImageView
|
||||
android:id="@+id/image_viewer_image"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -31,17 +31,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/item_attachment_thumbnail"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:src="@drawable/material_00_32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/item_attachment_broken"
|
||||
@@ -50,7 +40,7 @@
|
||||
android:contentDescription="@string/entry_attachments"
|
||||
android:src="@drawable/ic_attach_file_broken_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/item_attachment_thumbnail"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
@@ -59,11 +49,23 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||
app:layout_constraintStart_toEndOf="@+id/item_attachment_broken"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_thumbnail"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="BinaryFile.attach" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/item_attachment_thumbnail"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||
app:layout_constraintStart_toEndOf="@+id/item_attachment_title"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/item_attachment_size_container"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user