diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index 8a4fe05a..a8bd0503 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -3,7 +3,6 @@ package com.tutpro.baresip import android.content.Context import android.database.Cursor import android.graphics.Bitmap -import android.graphics.BitmapFactory import android.net.Uri import android.provider.ContactsContract import androidx.core.net.toUri @@ -302,7 +301,7 @@ sealed class Contact { val avatarFilePath = BaresipService.filesPath + "/$id.png" if (File(avatarFilePath).exists()) { try { - contact.avatarImage = BitmapFactory.decodeFile(avatarFilePath) + contact.avatarImage = Utils.decodeSampledBitmapFromFile(avatarFilePath, 192, 192) Log.d(TAG, "Set avatarImage") if (contact.avatarImage == null) Log.d(TAG, "Contact $id avatarImage is null") diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt index 4b3c2a27..6350879e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt @@ -468,9 +468,7 @@ private fun AvatarSection( rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> if (uri != null) { try { - val inputStream = ctx.contentResolver.openInputStream(uri) - val avatarBitmap = BitmapFactory.decodeStream(inputStream) - inputStream?.close() + val avatarBitmap = Utils.decodeSampledBitmapFromUri(ctx, uri, 192, 192) if (avatarBitmap == null) { Log.e(TAG, "Failed to decode bitmap from URI: $uri") diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt index 2f539224..a9ca7540 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap -import android.graphics.BitmapFactory +import androidx.core.graphics.scale import android.provider.ContactsContract import android.util.Base64 import android.util.Log @@ -243,11 +243,17 @@ private fun ContactsScreen(navController: NavController) { if (photoBase64.isNotEmpty()) try { val decodedString = Base64.decode(photoBase64, Base64.DEFAULT) - val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size) + val decodedByte = Utils.decodeSampledBitmapFromByteArray(decodedString, 192, 192) if (decodedByte != null) { + val scaledByte = decodedByte.scale(192, 192) val avatarFile = File(BaresipService.filesPath, "$contactId.png") - FileOutputStream(avatarFile).use { out -> - decodedByte.compress(Bitmap.CompressFormat.PNG, 100, out) + try { + val out = FileOutputStream(avatarFile) + scaledByte.compress(Bitmap.CompressFormat.PNG, 100, out) + out.flush() + out.close() + } catch (e: Exception) { + Log.e(TAG, "Failed to save photo for $name: ${e.message}") } } } catch (e: Exception) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index 74f4f5bc..7c229705 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -15,6 +15,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode @@ -1390,6 +1391,57 @@ object Utils { } } + fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { + val (height: Int, width: Int) = options.outHeight to options.outWidth + var inSampleSize = 1 + if (height > reqHeight || width > reqWidth) { + val halfHeight = height / 2 + val halfWidth = width / 2 + while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) + inSampleSize *= 2 + } + return inSampleSize + } + + fun decodeSampledBitmapFromUri(ctx: Context, uri: Uri, reqWidth: Int, reqHeight: Int): Bitmap? { + return try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + ctx.contentResolver.openInputStream(uri).use { BitmapFactory.decodeStream(it, null, options) } + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight) + options.inJustDecodeBounds = false + ctx.contentResolver.openInputStream(uri).use { BitmapFactory.decodeStream(it, null, options) } + } catch (e: Exception) { + Log.e(TAG, "Failed to decode sampled bitmap from URI: ${e.message}") + null + } + } + + fun decodeSampledBitmapFromByteArray(data: ByteArray, reqWidth: Int, reqHeight: Int): Bitmap? { + return try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(data, 0, data.size, options) + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight) + options.inJustDecodeBounds = false + BitmapFactory.decodeByteArray(data, 0, data.size, options) + } catch (e: Exception) { + Log.e(TAG, "Failed to decode sampled bitmap from ByteArray: ${e.message}") + null + } + } + + fun decodeSampledBitmapFromFile(path: String, reqWidth: Int, reqHeight: Int): Bitmap? { + return try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeFile(path, options) + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight) + options.inJustDecodeBounds = false + BitmapFactory.decodeFile(path, options) + } catch (e: Exception) { + Log.e(TAG, "Failed to decode sampled bitmap from file: ${e.message}") + null + } + } + @RequiresApi(29) @SuppressLint("HardwareIds") fun getLine1Number(ctx: Context, subscriptionId: Int = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID): String? {