diff --git a/CHANGELOG b/CHANGELOG index c2efb6e68..9543ff5e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ KeePassDX(2.9.14) * Dark Themes #532 #714 * Fix binary deduplication #715 * Fix IconId #901 + * Resize image stream dynamically to prevent slowdown #919 * Small changes #795 #900 #903 #909 #914 KeePassDX(2.9.13) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt index fd54fc4aa..4d1b1bc00 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt @@ -34,6 +34,8 @@ 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 com.kunzisoft.keepass.tasks.BinaryStreamManager +import kotlin.math.max class ImageViewerActivity : LockingActivity() { @@ -51,6 +53,12 @@ class ImageViewerActivity : LockingActivity() { val imageView: ImageView = findViewById(R.id.image_viewer_image) val progressView: View = findViewById(R.id.image_viewer_progress) + // Approximately, to not OOM and allow a zoom + val mImagePreviewMaxWidth = max( + resources.displayMetrics.widthPixels * 2, + resources.displayMetrics.heightPixels * 2 + ) + try { progressView.visibility = View.VISIBLE intent.getParcelableExtra(IMAGE_ATTACHMENT_TAG)?.let { attachment -> @@ -58,7 +66,11 @@ class ImageViewerActivity : LockingActivity() { supportActionBar?.title = attachment.name supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryFile.length) - Attachment.loadBitmap(attachment, Database.getInstance().loadedCipherKey) { bitmapLoaded -> + BinaryStreamManager.loadBitmap( + attachment.binaryFile, + Database.getInstance().loadedCipherKey, + mImagePreviewMaxWidth + ) { bitmapLoaded -> if (bitmapLoaded == null) { finish() } else { diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt index da7445365..bcdde6d1e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt @@ -32,13 +32,14 @@ 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 import com.kunzisoft.keepass.model.StreamDirection +import com.kunzisoft.keepass.tasks.BinaryStreamManager import com.kunzisoft.keepass.view.expand +import kotlin.math.max class EntryAttachmentsItemsAdapter(context: Context) @@ -48,6 +49,11 @@ class EntryAttachmentsItemsAdapter(context: Context) var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null + // Approximately + private val mImagePreviewMaxWidth = max( + context.resources.displayMetrics.widthPixels, + context.resources.getDimensionPixelSize(R.dimen.item_file_info_height) + ) private var mTitleColor: Int init { @@ -76,7 +82,11 @@ class EntryAttachmentsItemsAdapter(context: Context) if (entryAttachmentState.previewState == AttachmentState.NULL) { entryAttachmentState.previewState = AttachmentState.IN_PROGRESS // Load the bitmap image - Attachment.loadBitmap(entryAttachmentState.attachment, binaryCipherKey) { imageLoaded -> + BinaryStreamManager.loadBitmap( + entryAttachmentState.attachment.binaryFile, + binaryCipherKey, + mImagePreviewMaxWidth + ) { imageLoaded -> if (imageLoaded == null) { entryAttachmentState.previewState = AttachmentState.ERROR visibility = View.GONE diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt index d0ac61491..0c0ad7bd4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt @@ -19,12 +19,10 @@ */ 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.BinaryFile -import kotlinx.coroutines.* + data class Attachment(var name: String, var binaryFile: BinaryFile) : Parcelable { @@ -68,28 +66,5 @@ data class Attachment(var name: String, override fun newArray(size: Int): Array { return arrayOfNulls(size) } - - fun loadBitmap(attachment: Attachment, - binaryCipherKey: Database.LoadedKey?, - actionOnFinish: (Bitmap?) -> Unit) { - CoroutineScope(Dispatchers.Main).launch { - withContext(Dispatchers.IO) { - val asyncResult: Deferred = async { - runCatching { - binaryCipherKey?.let { binaryKey -> - var bitmap: Bitmap? - attachment.binaryFile.getUnGzipInputDataStream(binaryKey).use { bitmapInputStream -> - bitmap = BitmapFactory.decodeStream(bitmapInputStream) - } - bitmap - } - }.getOrNull() - } - withContext(Dispatchers.Main) { - actionOnFinish(asyncResult.await()) - } - } - } - } } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryStreamManager.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryStreamManager.kt index fb3401ab3..dd991e2e7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryStreamManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryStreamManager.kt @@ -9,10 +9,15 @@ import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.database.BinaryFile import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.utils.UriUtil +import kotlinx.coroutines.* import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream +import kotlin.math.ceil +import kotlin.math.ln +import kotlin.math.max +import kotlin.math.pow object BinaryStreamManager { @@ -126,6 +131,57 @@ object BinaryStreamManager { return Bitmap.createScaledBitmap(this, width, height, true) } + fun loadBitmap(binaryFile: BinaryFile, + binaryCipherKey: Database.LoadedKey?, + maxWidth: Int, + actionOnFinish: (Bitmap?) -> Unit) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val asyncResult: Deferred = async { + runCatching { + binaryCipherKey?.let { binaryKey -> + val bitmap: Bitmap? = decodeSampledBitmap(binaryFile, + binaryKey, + maxWidth) + bitmap + } + }.getOrNull() + } + withContext(Dispatchers.Main) { + actionOnFinish(asyncResult.await()) + } + } + } + } + + private fun decodeSampledBitmap(binaryFile: BinaryFile, + binaryCipherKey: Database.LoadedKey, + maxWidth: Int): Bitmap? { + // First decode with inJustDecodeBounds=true to check dimensions + return BitmapFactory.Options().run { + try { + inJustDecodeBounds = true + binaryFile.getUnGzipInputDataStream(binaryCipherKey).use { + BitmapFactory.decodeStream(it, null, this) + } + // Calculate inSampleSize + var scale = 1 + if (outHeight > maxWidth || outWidth > maxWidth) { + scale = 2.0.pow(ceil(ln(maxWidth / max(outHeight, outWidth).toDouble()) / ln(0.5))).toInt() + } + inSampleSize = scale + + // Decode bitmap with inSampleSize set + inJustDecodeBounds = false + binaryFile.getUnGzipInputDataStream(binaryCipherKey).use { + BitmapFactory.decodeStream(it, null, this) + } + } catch (e: Exception) { + null + } + } + } + private const val DEFAULT_ICON_WIDTH = 64 private val TAG = BinaryStreamManager::class.java.name diff --git a/app/src/main/res/layout/item_attachment.xml b/app/src/main/res/layout/item_attachment.xml index 95d69f18b..c1f50d4f6 100644 --- a/app/src/main/res/layout/item_attachment.xml +++ b/app/src/main/res/layout/item_attachment.xml @@ -31,7 +31,7 @@ 18dp 8dp 12dp + 144dp 6dp 12dp 14dp diff --git a/fastlane/metadata/android/en-US/changelogs/58.txt b/fastlane/metadata/android/en-US/changelogs/58.txt index 3c515014d..a57fa0934 100644 --- a/fastlane/metadata/android/en-US/changelogs/58.txt +++ b/fastlane/metadata/android/en-US/changelogs/58.txt @@ -2,4 +2,5 @@ * Dark Themes #532 #714 * Fix binary deduplication #715 * Fix IconId #901 + * Resize image stream dynamically to prevent slowdown #919 * Small changes #795 #900 #903 #909 #914 \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/58.txt b/fastlane/metadata/android/fr-FR/changelogs/58.txt index 7c8defa70..d1d1f9325 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/58.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/58.txt @@ -2,4 +2,5 @@ * Thèmes sombres #532 #714 * Correction de la duplication des binaires #715 * Correction IconId #901 + * Changement de taille dynamique du flux d'image pour prévenir les ralentissements #919 * Petits changements #795 #900 #903 #909 #914 \ No newline at end of file