Add thread to load images

This commit is contained in:
J-Jamet
2021-02-08 13:42:04 +01:00
parent 064c468e62
commit 8752f92cea
11 changed files with 103 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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