diff --git a/jami-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt b/jami-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
index bd083dbc4ddb36479f324768dd820173201b420c..293fdab2c6bb67b3e01b9323e24de64fb871ad40 100644
--- a/jami-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
+++ b/jami-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
@@ -27,8 +27,12 @@ import io.reactivex.rxjava3.schedulers.Schedulers
 
 object DeviceUtils {
     fun isTv(context: Context): Boolean =
-        context.getSystemService(UiModeManager::class.java)
-            .currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
+        try {
+            context.getSystemService(UiModeManager::class.java)
+                .currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
+        } catch (e: Throwable) {
+            false
+        }
 
     fun getStatusBarHeight(context: Context): Int {
         val resourceId = context.resources
diff --git a/jami-android/app/src/main/java/cx/ring/views/AvatarView.kt b/jami-android/app/src/main/java/cx/ring/views/AvatarView.kt
new file mode 100644
index 0000000000000000000000000000000000000000..42c5487002c4e4ed3fdeb70198794f2558b0fba3
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/views/AvatarView.kt
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package cx.ring.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.content.res.use
+import cx.ring.R
+import cx.ring.utils.BitmapUtils
+import kotlin.math.min
+
+class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
+    View(context, attrs, defStyleAttr, defStyleRes) {
+
+    private var avatarDrawable: AvatarDrawable? = null
+
+    init {
+        context.obtainStyledAttributes(attrs, R.styleable.AvatarView).use { typedArray ->
+            val uri = typedArray.getString(R.styleable.AvatarView_uri)
+            if (uri != null || isInEditMode) {
+                val username = typedArray.getString(R.styleable.AvatarView_username)
+                val displayName = typedArray.getString(R.styleable.AvatarView_displayName)
+                val avatar = typedArray.getDrawable(R.styleable.AvatarView_avatar)
+                val cropCircle = typedArray.getBoolean(R.styleable.AvatarView_cropCircle, true)
+                setAvatar(AvatarDrawable.Builder()
+                    .withId(uri)
+                    .withNameData(displayName, username)
+                    .withPhoto(avatar?.let { BitmapUtils.drawableToBitmap(it) })
+                    .withCircleCrop(cropCircle)
+                    .build(context))
+            }
+        }
+    }
+
+    /** Returns true if the avatar was set if it was previously empty. */
+    fun setAvatar(avatar: AvatarDrawable?): Boolean {
+        val noPrevious = avatarDrawable == null
+        avatarDrawable = avatar
+        avatar?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
+        invalidate()
+        return noPrevious
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
+        val measuredHeight = MeasureSpec.getSize(heightMeasureSpec)
+        val paddingW = paddingLeft + paddingRight
+        val paddingH = paddingTop + paddingBottom
+        val size = min(measuredWidth - paddingW, measuredHeight - paddingH)
+        setMeasuredDimension(size + paddingW, size + paddingH)
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        avatarDrawable?.setBounds(
+            paddingLeft,
+            paddingTop,
+            w - paddingRight,
+            h - paddingBottom
+        )
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        avatarDrawable?.draw(canvas)
+    }
+}
\ No newline at end of file
diff --git a/jami-android/app/src/main/res/values/attrs.xml b/jami-android/app/src/main/res/values/attrs.xml
index 8e026b93603e7f2433a631ae021e6a8d397a7463..9851c8dc995c39548bda6d21f0e9ab6da0577566 100644
--- a/jami-android/app/src/main/res/values/attrs.xml
+++ b/jami-android/app/src/main/res/values/attrs.xml
@@ -11,6 +11,14 @@
         <attr name="text" format="string" />
     </declare-styleable>
 
+    <declare-styleable name="AvatarView">
+        <attr name="uri" format="string" />
+        <attr name="username" format="string" />
+        <attr name="displayName" format="string" />
+        <attr name="avatar" format="reference" />
+        <attr name="cropCircle" format="boolean" />
+    </declare-styleable>
+
     <declare-styleable name="SwitchButton">
         <attr name="backColor" format="color|reference"/>
         <attr name="status" format="string"/>