diff --git a/jami-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt b/jami-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
index 9fed25010aa0e941682ca79ad7885485cf19e56c..c1d93a36c6c211af477ccdba4092694b91f0fa6b 100644
--- a/jami-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
+++ b/jami-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
@@ -450,7 +450,7 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe
     private fun loadVCardContactData(contact: Contact, accountId: String): Single<Profile> =
         Single.fromCallable {
             val id = Base64.encodeToString(contact.primaryNumber.toByteArray(), Base64.NO_WRAP)
-            VCardServiceImpl.readData(VCardUtils.loadPeerProfileFromDisk(mContext.filesDir, mContext.cacheDir, id, accountId))
+            VCardServiceImpl.loadPeerProfileFromDisk(mContext.filesDir, mContext.cacheDir, id, accountId)
         }
             .subscribeOn(Schedulers.io())
 
diff --git a/jami-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt b/jami-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
index 7f4410f267303e5062efaadd86966dc1f43e1e60..dcee634bd2672ecd8161ad1725b123bca15a2cef 100644
--- a/jami-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
+++ b/jami-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
@@ -34,6 +34,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
 import net.jami.model.Account
 import net.jami.model.Profile
 import java.io.File
+import java.io.IOException
 
 class VCardServiceImpl(private val mContext: Context) : VCardService() {
     override fun loadProfile(account: Account): Observable<Profile> = loadProfile(mContext, account)
@@ -82,8 +83,7 @@ class VCardServiceImpl(private val mContext: Context) : VCardService() {
             synchronized(account) {
                 var ret = account.loadedProfile
                 if (ret == null) {
-                    ret = VCardUtils.loadLocalProfileFromDiskWithDefault(context.filesDir, account.accountId)
-                        .map { vcard: VCard -> readData(vcard) }
+                    ret = loadLocalProfileFromDiskWithDefault(context.filesDir, context.cacheDir, account.accountId)
                         .subscribeOn(Schedulers.io())
                         .cache()
                     account.loadedProfile = ret
@@ -100,5 +100,70 @@ class VCardServiceImpl(private val mContext: Context) : VCardService() {
 
         fun readData(profile: Pair<String?, ByteArray?>): Profile =
             Profile(profile.first, BitmapUtils.bytesToBitmap(profile.second))
+
+        @Throws(IOException::class)
+        fun loadPeerProfileFromDisk(filesDir: File, cacheDir: File, filename: String, accountId: String): Profile {
+            val cacheFolder = VCardUtils.peerProfileCachePath(cacheDir, accountId)
+            val cacheName = File(cacheFolder, "$filename.txt")
+            val cachePicture = File(cacheFolder, filename)
+            val profileFile = File(VCardUtils.peerProfilePath(filesDir, accountId), "$filename.vcf")
+            return loadProfileWithCache(cacheName, cachePicture, profileFile)
+        }
+
+        @Throws(IOException::class)
+        fun loadLocalProfileFromDisk(filesDir: File, cacheDir: File, accountId: String): Profile {
+            val cacheFolder = VCardUtils.localProfileCachePath(cacheDir, accountId)
+            val cacheName = File(cacheFolder, "${VCardUtils.ACCOUNT_PROFILE_NAME}.txt")
+            val cachePicture = File(cacheFolder, "${VCardUtils.ACCOUNT_PROFILE_NAME}_pic")
+            val profileFile = File(VCardUtils.localProfilePath(filesDir, accountId), VCardUtils.LOCAL_USER_VCARD_NAME)
+            return loadProfileWithCache(cacheName, cachePicture, profileFile)
+        }
+        fun loadLocalProfileFromDiskWithDefault(filesDir: File, cacheDir: File, accountId: String): Single<Profile> =
+            Single.fromCallable { loadLocalProfileFromDisk(filesDir, cacheDir, accountId) }
+                .onErrorReturn { Profile.EMPTY_PROFILE }
+
+        fun loadProfileWithCache(cacheName: File, cachePicture: File, profileFile: File): Profile {
+            // Case 1: no profile for this peer
+            if (!profileFile.exists()) {
+                return Profile.EMPTY_PROFILE
+            }
+
+            // Case 2: read profile from cache
+            if (cacheName.exists() && cacheName.lastModified() >= profileFile.lastModified()) {
+                return Profile(
+                    cacheName.readText(),
+                    if (cachePicture.exists()) BitmapUtils.bytesToBitmap(cachePicture.readBytes()) else null
+                )
+            }
+
+            // Case 3: read profile from disk and update cache
+            val (name, picture) = VCardUtils.readData(VCardUtils.loadFromDisk(profileFile))
+            cacheName.writeText(name ?: "")
+            if (picture != null) {
+                BitmapUtils.bytesToBitmap(picture)?.let { bitmap ->
+                    BitmapUtils.createScaledBitmap(bitmap, 512).apply {
+                        if (this === bitmap) {
+                            // Case 3a: bitmap is already small enough, cache it as-is
+                            Schedulers.io().createWorker().schedule {
+                                cachePicture.outputStream().use {
+                                    it.write(picture)
+                                }
+                            }
+                            return Profile(name, bitmap)
+                        }
+                        bitmap.recycle()
+                    }
+                }?.let { scaledBitmap ->
+                    // Case 3b: bitmap is too big, reduce it and write to cache
+                    Schedulers.io().createWorker().schedule {
+                        cachePicture.outputStream().use {
+                            scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 88, it)
+                        }
+                    }
+                    return Profile(name, scaledBitmap)
+                }
+            }
+            return Profile(name, null)
+        }
     }
 }
\ No newline at end of file
diff --git a/jami-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt b/jami-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
index 02bd99da143b53a6a17f1d6d5b8a011a007a9c92..781874eb4b7e2bf20606df135f2ccea0c1af3516 100644
--- a/jami-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
+++ b/jami-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
@@ -35,6 +35,8 @@ import ezvcard.property.Photo
 import net.jami.utils.QRCodeUtils
 import java.io.ByteArrayOutputStream
 import java.nio.ByteBuffer
+import androidx.core.graphics.scale
+import androidx.core.graphics.createBitmap
 
 /**
  * Helper calls to manipulates Bitmaps
@@ -103,13 +105,16 @@ object BitmapUtils {
         while (ratio * ratio < minRatio) ratio *= 2
         height /= ratio
         width /= ratio
-        val ret = Bitmap.createScaledBitmap(bmp, width, height, true)
+        val ret = bmp.scale(width, height)
         Log.d(TAG, "reduceBitmap: bitmap size after x" + ratio + " reduce " + ret.byteCount)
         return ret
     }
 
-    fun createScaledBitmap(bitmap: Bitmap?, maxSize: Int): Bitmap {
-        require(!(bitmap == null || maxSize < 0))
+    fun createScaledBitmap(bitmap: Bitmap, maxSize: Int): Bitmap {
+        require(maxSize >= 0)
+        Log.w(TAG, "createScaledBitmap: ${bitmap.width}x${bitmap.height} -> $maxSize")
+        if (bitmap.width <= maxSize && bitmap.height <= maxSize)
+            return bitmap
         var width = bitmap.height
         var height = bitmap.width
         if (width != height) {
@@ -126,7 +131,7 @@ object BitmapUtils {
             width = maxSize
             height = maxSize
         }
-        return Bitmap.createScaledBitmap(bitmap, width, height, true)
+        return bitmap.scale(width, height)
     }
 
     fun drawableToBitmap(drawable: Drawable, size: Int = -1, padding: Int = 0): Bitmap {
@@ -135,8 +140,7 @@ object BitmapUtils {
         }
         val width = drawable.intrinsicWidth.takeIf { it > 0 } ?: size
         val height = drawable.intrinsicHeight.takeIf { it > 0 } ?: size
-        val bitmap =
-            Bitmap.createBitmap(width + 2 * padding, height + 2 * padding, Bitmap.Config.ARGB_8888)
+        val bitmap = createBitmap(width + 2 * padding, height + 2 * padding)
         val canvas = Canvas(bitmap)
         drawable.setBounds(padding, padding, canvas.width - padding, canvas.height - padding)
         drawable.draw(canvas)
@@ -162,7 +166,7 @@ object BitmapUtils {
 
     private fun pageToBitmap(page: Page, maxWidth: Int, maxHeight: Int): Bitmap =
         pageRenderSize(page, maxWidth, maxHeight)
-            .let { (w, h) -> Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) }
+            .let { (w, h) -> createBitmap(w, h) }
             .apply { page.render(this, null, null, Page.RENDER_MODE_FOR_DISPLAY) }
 
     fun documentToBitmap(context: Context, uri: Uri, maxWidth: Int = -1, maxHeight: Int = -1): Bitmap? =
@@ -187,7 +191,7 @@ object BitmapUtils {
         drawableToBitmap(drawable, size, size / 5)
 
     fun qrToBitmap(qrCodeData: QRCodeUtils.QRCodeData) =
-        Bitmap.createBitmap(qrCodeData.width, qrCodeData.height, Bitmap.Config.ARGB_8888).apply {
+        createBitmap(qrCodeData.width, qrCodeData.height).apply {
             setPixels(qrCodeData.data, 0, qrCodeData.width, 0, 0, qrCodeData.width, qrCodeData.height)
         }
 
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/VCardUtils.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/VCardUtils.kt
index 8a1db5d9ea7775a03fd9c3a0bb456ddb2ef5c748..69f56965dbe39132185a491f762589d2d5388870 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/VCardUtils.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/VCardUtils.kt
@@ -16,7 +16,6 @@
  */
 package net.jami.utils
 
-import net.jami.utils.FileUtils.moveFile
 import ezvcard.VCard
 import ezvcard.property.FormattedName
 import ezvcard.property.Uid
@@ -38,7 +37,8 @@ import java.util.HashMap
 object VCardUtils {
     val TAG = VCardUtils::class.simpleName!!
     const val VCARD_KEY_MIME_TYPE = "mimeType"
-    const val LOCAL_USER_VCARD_NAME = "profile.vcf"
+    const val ACCOUNT_PROFILE_NAME = "profile"
+    const val LOCAL_USER_VCARD_NAME = "$ACCOUNT_PROFILE_NAME.vcf"
     private const val VCARD_MAX_SIZE = 1024L * 1024L * 8
 
     fun readData(vcard: VCard?): Pair<String?, ByteArray?> {
@@ -136,36 +136,6 @@ object VCardUtils {
             else -> "JPEG"
         }
 
-    @Throws(IOException::class)
-    fun loadPeerProfileFromDisk(filesDir: File, cacheDir: File, filename: String, accountId: String): Pair<String?, ByteArray?> {
-        val cacheFolder = peerProfileCachePath(cacheDir, accountId)
-        val cacheName = File(cacheFolder, filename + ".txt")
-        val cachePicture = File(cacheFolder, filename)
-        val profileFile = File(peerProfilePath(filesDir, accountId), filename + ".vcf")
-
-        // Case 1: no profile for this peer
-        if (!profileFile.exists()) {
-            return Pair(null, null)
-        }
-
-        // Case 2: read profile from cache
-        if (cacheName.exists() && cacheName.lastModified() > profileFile.lastModified()) {
-            return Pair(
-                cacheName.readText(),
-                if (cachePicture.exists()) cachePicture.readBytes() else null
-            )
-        }
-
-        // Case 3: read profile from disk and update cache
-        val data = readData(loadFromDisk(profileFile))
-        val (name, picture) = data
-        cacheName.writeText(name ?: "")
-        if (picture != null) {
-            cachePicture.writeBytes(picture)
-        }
-        return data
-    }
-
     fun resetCustomProfileName(accountId: String, filename: String, filesDir: File) {
         if (filename.isEmpty()) {
             return
@@ -268,18 +238,21 @@ object VCardUtils {
         return File(accountDir, "CustomPeerProfiles").apply { mkdirs() }
     }
 
-    private fun peerProfilePath(filesDir: File, accountId: String): File {
+    fun peerProfilePath(filesDir: File, accountId: String): File {
         val accountDir = File(filesDir, accountId)
         return File(accountDir, "profiles").apply { mkdirs() }
     }
-    private fun peerProfileCachePath(cacheDir: File, accountId: String): File {
+    fun peerProfileCachePath(cacheDir: File, accountId: String): File {
         val accountDir = File(cacheDir, accountId)
         return File(accountDir, "profiles").apply { mkdirs() }
     }
 
-    private fun localProfilePath(filesDir: File, accountId: String): File =
+    fun localProfilePath(filesDir: File, accountId: String): File =
         File(filesDir, accountId).apply { mkdir() }
 
+    fun localProfileCachePath(cacheDir: File, accountId: String): File =
+        File(cacheDir, accountId).apply { mkdir() }
+
     private fun defaultProfile(accountId: String): VCard = VCard().apply { Uid(accountId) }
 
     fun loadProfile(vcard: File): Single<VCard> =