avatar: refactor drawing code

Change-Id: I85258ec690aa0d7328d07300e12d9ea1a316ab39
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 2f2d10a..8b69d7c 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -260,7 +260,7 @@
         </activity>
         <activity
             android:name=".client.ConversationActivity"
-            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+            android:configChanges="screenSize|screenLayout|smallestScreenSize"
             android:label="@string/app_name"
             android:parentActivityName=".client.HomeActivity"
             android:screenOrientation="fullUser"
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
index 3325a82..fd14121 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
@@ -48,6 +48,7 @@
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.resource.bitmap.CenterInside;
 import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.target.DrawableImageViewTarget;
 
 import java.io.File;
 import java.text.DateFormat;
@@ -86,7 +87,6 @@
     private final ArrayList<ConversationElement> mConversationElements = new ArrayList<>();
     private final ConversationPresenter presenter;
     private final ConversationFragment conversationFragment;
-    private byte[] mPhoto;
     private final ColorStateList mErrorColor;
     private final int hPadding;
     private final int vPadding;
@@ -161,11 +161,8 @@
 
     /**
      * Updates the contact photo to use for this conversation
-     *
-     * @param photo contact photo to display.
      */
-    public void setPhoto(byte[] photo) {
-        mPhoto = photo;
+    public void setPhoto() {
         notifyDataSetChanged();
     }
 
@@ -352,7 +349,7 @@
             GlideApp.with(context)
                     .load(path)
                     .apply(PICTURE_OPTIONS)
-                    .into(conversationViewHolder.mPhoto);
+                    .into(new DrawableImageViewTarget(conversationViewHolder.mPhoto).waitForLayout());
 
             ((LinearLayout)conversationViewHolder.mAnswerLayout).setGravity(file.isOutgoing() ? Gravity.END : Gravity.START);
             conversationViewHolder.mPhoto.setOnClickListener(v -> {
@@ -444,17 +441,7 @@
         final Context context = convViewHolder.itemView.getContext();
 
         if (textMessage.isIncoming() && !sameAsPreviousMsg) {
-            Drawable contactPicture = AvatarFactory.getAvatar(
-                    context,
-                    mPhoto,
-                    contact.getUsername(),
-                    textMessage.getNumberUri().getHost());
-
-            Glide.with(context)
-                    .load(contactPicture)
-                    .apply(AvatarFactory.getGlideOptions(true, true))
-                    //.transition(DrawableTransitionOptions.withCrossFade())
-                    .into(convViewHolder.mPhoto);
+            AvatarFactory.loadGlideAvatar(convViewHolder.mPhoto, contact);
         }
 
         switch (textMessage.getStatus()) {
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
index b494423..5d61724 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
@@ -47,6 +47,7 @@
 import cx.ring.model.TextMessage;
 import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.viewholders.SmartListViewHolder;
+import cx.ring.views.AvatarDrawable;
 
 public class SmartListAdapter extends RecyclerView.Adapter<SmartListViewHolder> {
 
@@ -98,22 +99,9 @@
             holder.convStatus.setTypeface(null, Typeface.NORMAL);
         }
 
-        final Drawable contactPicture = AvatarFactory.getAvatar(
-                holder.itemView.getContext(),
-                contact.getPhoto(),
-                smartListViewModel.getContactName(),
-                smartListViewModel.getUuid());
-        if (contactPicture != holder.picture) {
-            holder.picture = contactPicture;
-            holder.photo.setImageDrawable(null);
-            Glide.with(holder.itemView.getContext())
-                    .load(contactPicture)
-                    .apply(AvatarFactory.getGlideOptions(true, true))
-                    .into(holder.photo);
-        }
-
+        holder.photo.setImageDrawable(new AvatarDrawable(holder.photo.getContext(), contact));
+        //AvatarFactory.loadGlideAvatar(holder.photo, contact);
         holder.online.setVisibility(smartListViewModel.isOnline() ? View.VISIBLE : View.GONE);
-
         holder.bind(listener, smartListViewModel);
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
index 480f9c1..821a178 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
@@ -22,6 +22,7 @@
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.support.v7.app.ActionBar;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.view.KeyEvent;
@@ -54,8 +55,9 @@
 
         ButterKnife.bind(this);
         setSupportActionBar(mToolbar);
-
-        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        ActionBar ab = getSupportActionBar();
+        if (ab != null)
+            ab.setDisplayHomeAsUpEnabled(true);
 
         Intent intent = getIntent();
         String action = intent == null ? null : intent.getAction();
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlackListViewHolder.java b/ring-android/app/src/main/java/cx/ring/contactrequests/BlackListViewHolder.java
index d59a66d..a55bb75 100644
--- a/ring-android/app/src/main/java/cx/ring/contactrequests/BlackListViewHolder.java
+++ b/ring-android/app/src/main/java/cx/ring/contactrequests/BlackListViewHolder.java
@@ -52,7 +52,9 @@
     }
 
     public void bind(final BlackListListeners clickListener, final CallContact contact) {
-        byte[] photo = contact.getPhoto();
+        AvatarFactory.loadGlideAvatar(mPhoto, contact);
+
+        /*byte[] photo = contact.getPhoto();
 
         Drawable contactPicture = AvatarFactory.getAvatar(
                 itemView.getContext(),
@@ -64,7 +66,7 @@
                 .load(contactPicture)
                 .apply(AvatarFactory.getGlideOptions(true, true))
                 .transition(DrawableTransitionOptions.withCrossFade())
-                .into(mPhoto);
+                .into(mPhoto);*/
 
         mDisplayname.setText(contact.getRingUsername());
 
diff --git a/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java b/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
index e1b3ca4..197f12e 100644
--- a/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
+++ b/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
@@ -2,6 +2,7 @@
  * Copyright (C) 2004-2018 Savoir-faire Linux Inc.
  *
  * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
+ * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
  *
  * 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
@@ -20,43 +21,36 @@
 
 package cx.ring.contacts;
 
+import android.app.Fragment;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v7.content.res.AppCompatResources;
-import android.util.Log;
-import android.util.LruCache;
 
+import android.text.TextUtils;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
 import com.bumptech.glide.request.RequestOptions;
 
 import cx.ring.R;
-import cx.ring.model.Uri;
+import cx.ring.model.CallContact;
 import cx.ring.utils.CircleTransform;
-import cx.ring.utils.HashUtils;
+import cx.ring.views.AvatarDrawable;
 import ezvcard.VCard;
+import ezvcard.property.FormattedName;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
 
 public class AvatarFactory {
 
-    private static final String TAG = AvatarFactory.class.getSimpleName();
-
-    // ordered to have the same colors on all clients
-    private static final int[] contactColors = {
-            R.color.red_500, R.color.pink_500,
-            R.color.purple_500, R.color.deep_purple_500,
-            R.color.indigo_500, R.color.blue_500,
-            R.color.cyan_500, R.color.teal_500,
-            R.color.green_500, R.color.light_green_500,
-            R.color.grey_500, R.color.lime_500,
-            R.color.amber_500, R.color.deep_orange_500,
-            R.color.brown_500, R.color.blue_grey_500
-    };
-    private static final int DEFAULT_AVATAR_SIZE = 128;
-
+    public static final int SIZE_AB = 36;
+    public static final int SIZE_NOTIF = 48;
+    
     private static final RequestOptions GLIDE_OPTIONS = new RequestOptions()
             .centerCrop()
             .error(R.drawable.ic_contact_picture_fallback);
@@ -66,149 +60,87 @@
             .error(R.drawable.ic_contact_picture_fallback)
             .transform(new CircleTransform());
 
-    private static final Paint AVATAR_TEXT_PAINT = new Paint();
-    static {
-        AVATAR_TEXT_PAINT.setTextAlign(Paint.Align.CENTER);
-        AVATAR_TEXT_PAINT.setColor(Color.WHITE);
-        AVATAR_TEXT_PAINT.setAntiAlias(true);
+    private AvatarFactory() {}
+
+    private static Drawable getDrawable(Context context, byte[] photo, String profileName, String username, String id) {
+        return new AvatarDrawable(context, photo, TextUtils.isEmpty(profileName) ? username : profileName, id, true);
     }
 
-    private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
-
-    // Use 1/8th of the available memory for this memory cache.
-    private static final LruCache<String, BitmapDrawable> mMemoryCache = new LruCache<String, BitmapDrawable>(maxMemory / 8) {
-        @Override
-        protected int sizeOf(String key, BitmapDrawable bitmap) {
-            return bitmap.getBitmap() == null ? 0 : bitmap.getBitmap().getByteCount() / 1024;
-        }
-    };
-
-    private AvatarFactory() {
+    private static <T> RequestBuilder<T> getGlideRequest(Context context, RequestBuilder<T> request, byte[] photo, String profileName, String username, String id) {
+        return request.load(getDrawable(context, photo, profileName, username, id));
     }
 
-    public static Drawable getAvatar(Context context, VCard vcard, String username, String ringId) {
-        return getAvatar(context, vcard, username, ringId, Float.valueOf(context.getResources().getDisplayMetrics().density * 128).intValue());
+    public static RequestBuilder<Drawable> getGlideAvatar(Context context, RequestManager manager, CallContact contact) {
+        return getGlideRequest(context, manager.asDrawable(), contact.getPhoto(), contact.getProfileName(), contact.getUsername(), contact.getPrimaryNumber());
     }
 
-    public static Drawable getAvatar(Context context, VCard vcard, String username, String ringId, int pictureSize) {
-        if (vcard == null || pictureSize <= 0) {
-            throw new IllegalArgumentException();
-        }
-
-        byte[] contactPhoto = null;
-        if (vcard.getPhotos() != null && !vcard.getPhotos().isEmpty()) {
-            contactPhoto = vcard.getPhotos().get(0).getData();
-        }
-
-        return getAvatar(context, contactPhoto, username, ringId, pictureSize, false);
+    public static Single<Drawable> getAvatar(Context context, CallContact contact) {
+        return Single.fromCallable(() -> new AvatarDrawable(context, contact));
     }
 
-    public static BitmapDrawable getAvatar(Context context, byte[] photo, String username, String ringId, boolean noCache) {
-        return getAvatar(context, photo, username, ringId, Float.valueOf(context.getResources().getDisplayMetrics().density * DEFAULT_AVATAR_SIZE).intValue(), noCache);
+    private static Bitmap drawableToBitmap(Drawable drawable, int size) {
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable)drawable).getBitmap();
+        }
+
+        int width = drawable.getIntrinsicWidth();
+        width = width > 0 ? width : size;
+        int height = drawable.getIntrinsicHeight();
+        height = height > 0 ? height : size;
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        return bitmap;
     }
 
-    public static BitmapDrawable getAvatar(Context context, byte[] photo, String username, String ringId) {
-        return getAvatar(context, photo, username, ringId, Float.valueOf(context.getResources().getDisplayMetrics().density * DEFAULT_AVATAR_SIZE).intValue(), false);
+    public static Single<Bitmap> getBitmapAvatar(Context context, CallContact contact, int size) {
+        return getAvatar(context, contact)
+                .map(d -> drawableToBitmap(d, size))
+                .subscribeOn(Schedulers.computation());
     }
 
-    public static BitmapDrawable getAvatar(Context context, byte[] photo, String username, String ringId, int pictureSize, boolean noCache) {
-        if (context == null || pictureSize <= 0) {
-            throw new IllegalArgumentException();
-        }
-        String key = ringId + pictureSize + username;
-        BitmapDrawable bmp = noCache ? null : mMemoryCache.get(key);
-        if (bmp != null)
-            return bmp;
-
-        Log.d(TAG, "getAvatar: username=" + username + ", ringid=" + ringId + ", pictureSize=" + pictureSize);
-
-        if (photo != null && photo.length > 0) {
-            bmp = new BitmapDrawable(context.getResources(), BitmapFactory.decodeByteArray(photo, 0, photo.length));
-            mMemoryCache.put(key, bmp);
-            return bmp;
+    public static RequestBuilder<Drawable> getGlideAvatar(Context context, RequestManager manager, VCard vcard, String username, String ringId) {
+        byte[] photo = null;
+        String profile = null;
+        if (vcard != null) {
+            if (vcard.getPhotos() != null && !vcard.getPhotos().isEmpty()) {
+                photo = vcard.getPhotos().get(0).getData();
+            }
+            FormattedName name = vcard.getFormattedName();
+            if (name != null) {
+                String n = name.getValue();
+                if (!TextUtils.isEmpty(n))
+                    profile = n;
+            }
         }
 
-        Uri uriUsername = new Uri(username);
-        Uri uri = new Uri(ringId);
-        Character firstCharacter = getFirstCharacter(uriUsername.getRawRingId());
-        if (uri.isEmpty() || uriUsername.isRingId() || firstCharacter == null) {
-            bmp = createDefaultAvatar(context, generateAvatarColor(uri.getRawUriString()), pictureSize);
-            mMemoryCache.put(key, bmp);
-            return bmp;
-        }
-
-        bmp = createLetterAvatar(context, firstCharacter, generateAvatarColor(uri.getRawUriString()), pictureSize);
-        mMemoryCache.put(key, bmp);
-        return bmp;
+        return getGlideRequest(context, manager.asDrawable(), photo, profile, username, ringId)
+                .transition(DrawableTransitionOptions.withCrossFade(100));
     }
 
-    private static BitmapDrawable createDefaultAvatar(Context context, int backgroundColor, int pictureSize) {
-        if (context == null || pictureSize <= 0) {
-            throw new IllegalArgumentException();
-        }
-
-        Bitmap canvasBitmap = Bitmap.createBitmap(pictureSize, pictureSize, Bitmap.Config.ARGB_8888);
-
-        Canvas canvas = new Canvas(canvasBitmap);
-        canvas.drawColor(context.getResources().getColor(backgroundColor));
-
-        Drawable drawable = AppCompatResources.getDrawable(context, R.drawable.ic_contact_picture_box_default);
-        if (drawable == null) {
-            Log.e(TAG, "Not able to get default drawable");
-        } else {
-            drawable.setBounds(0, 0, pictureSize, pictureSize);
-            drawable.draw(canvas);
-        }
-
-        return new BitmapDrawable(context.getResources(), canvasBitmap);
+    public static RequestBuilder<Drawable> getGlideAvatar(Fragment fragment, CallContact contact) {
+        return getGlideAvatar(fragment.getActivity(), Glide.with(fragment), contact);
     }
 
-    private static BitmapDrawable createLetterAvatar(Context context, char firstCharacter, int backgroundColor, int pictureSize) {
-        if (context == null || pictureSize <= 0) {
-            throw new IllegalArgumentException();
-        }
-
-        Bitmap canvasBitmap = Bitmap.createBitmap(pictureSize, pictureSize, Bitmap.Config.ARGB_8888);
-
-        Canvas canvas = new Canvas(canvasBitmap);
-        canvas.drawColor(context.getResources().getColor(backgroundColor));
-
-        AVATAR_TEXT_PAINT.setTextSize(pictureSize / 2);
-        canvas.drawText(Character.toString(firstCharacter), pictureSize * 0.51f, pictureSize * 0.7f, AVATAR_TEXT_PAINT);
-
-        return new BitmapDrawable(context.getResources(), canvasBitmap);
+    public static RequestBuilder<Drawable> getGlideAvatar(Context context, CallContact contact) {
+        return getGlideAvatar(context, Glide.with(context), contact);
     }
 
-    private static int generateAvatarColor(String ringId) {
-        if (ringId == null) {
-            return R.color.grey_500;
-        }
-
-        String md5 = HashUtils.md5(ringId);
-        if (md5 == null) {
-            return R.color.grey_500;
-        }
-        int colorIndex = Integer.parseInt(md5.charAt(0) + "", 16);
-        Log.d(TAG, "generateAvatarColor: ringid=" + ringId + ", index=" + colorIndex + ", md5=" + md5);
-        return contactColors[colorIndex % contactColors.length];
+    public static void loadGlideAvatar(ImageView view, CallContact contact) {
+        getGlideAvatar(view.getContext(), contact).into(view);
     }
 
-    private static Character getFirstCharacter(String name) {
-        if (name == null) {
-            return null;
-        }
-        String filteredName = name.replaceAll("\\W+", "");
-        if (filteredName.isEmpty()) {
-            return null;
-        }
-        return Character.toUpperCase(name.charAt(0));
+    public static RequestBuilder<Bitmap> getBitmapGlideAvatar(Context context, CallContact contact) {
+        return getGlideRequest(context, Glide.with(context).asBitmap(), contact.getPhoto(), contact.getProfileName(), contact.getUsername(), contact.getPrimaryNumber());
     }
 
-    public static RequestOptions getGlideOptions(boolean circle, boolean withPlaceholder) {
+    public static RequestOptions getGlideOptions(boolean circle) {
         return circle ? GLIDE_OPTIONS_CIRCLE : GLIDE_OPTIONS;
     }
 
     public static void clearCache() {
-        mMemoryCache.evictAll();
     }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
index 52412bd..9ec6f9f 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
@@ -53,7 +53,6 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import com.bumptech.glide.Glide;
 import com.rodolfonavalon.shaperipplelibrary.ShapeRipple;
 import com.rodolfonavalon.shaperipplelibrary.model.Circle;
 
@@ -83,10 +82,7 @@
 import cx.ring.utils.Log;
 import cx.ring.utils.MediaButtonsHelper;
 import cx.ring.views.CheckableImageButton;
-import io.reactivex.Single;
-import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.schedulers.Schedulers;
 
 public class CallFragment extends BaseFragment<CallPresenter> implements CallView, MediaButtonsHelper.MediaButtonsHelperCallback {
 
@@ -509,7 +505,9 @@
             contactBubbleTxt.setText(username);
         }
 
-        mCompositeDisposable.add(Single.fromCallable(() -> Glide.with(getActivity())
+        AvatarFactory.loadGlideAvatar(contactBubbleView, contact);
+
+        /*mCompositeDisposable.add(Single.fromCallable(() -> Glide.with(getActivity())
                 .load(AvatarFactory.getAvatar(
                         getActivity(),
                         contact.getPhoto(),
@@ -520,7 +518,7 @@
                 .get())
                 .subscribeOn(Schedulers.computation())
                 .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(d -> contactBubbleView.setImageDrawable(d)));
+                .subscribe(d -> contactBubbleView.setImageDrawable(d)));*/
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
index 34b3cd2..3fcd9d9 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
@@ -25,9 +25,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.content.FileProvider;
@@ -68,6 +71,7 @@
 import cx.ring.client.CallActivity;
 import cx.ring.client.ConversationActivity;
 import cx.ring.client.HomeActivity;
+import cx.ring.contacts.AvatarFactory;
 import cx.ring.conversation.ConversationPresenter;
 import cx.ring.conversation.ConversationView;
 import cx.ring.dependencyinjection.RingInjectionComponent;
@@ -86,6 +90,8 @@
 import cx.ring.utils.ContentUriHandler;
 import cx.ring.utils.MediaButtonsHelper;
 import cx.ring.views.MessageEditText;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
 
 import static android.app.Activity.RESULT_OK;
 
@@ -160,6 +166,7 @@
     private SharedPreferences mPreferences;
 
     private File mCurrentPhoto = null;
+    private Disposable actionbarTarget = null;
 
     private Handler mHandler = new Handler();
     private static final int REFRESH_INTERVAL = 10000;
@@ -455,11 +462,19 @@
     }
 
     @Override
+    public void onDetach() {
+        if (actionbarTarget != null) {
+            actionbarTarget.dispose();
+            actionbarTarget = null;
+        }
+        super.onDetach();
+    }
+
+    @Override
     public void onDestroy() {
         if (mDeleteConversation) {
             mDeleteDialog.dismiss();
         }
-
         super.onDestroy();
     }
 
@@ -536,17 +551,30 @@
     }
 
     @Override
-    public void displayContactName(final CallContact contact) {
+    public void displayContact(final CallContact contact) {
         ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
         if (actionBar == null) {
             return;
         }
+        Context context = actionBar.getThemedContext();
         String displayName = contact.getDisplayName();
         actionBar.setTitle(displayName);
+
+        if (actionbarTarget != null) {
+            actionbarTarget.dispose();
+            actionbarTarget = null;
+        }
+        int targetSize = (int) (AvatarFactory.SIZE_AB * context.getResources().getDisplayMetrics().density);
+        actionbarTarget = AvatarFactory.getBitmapAvatar(context, contact, targetSize)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(b -> actionBar.setLogo(new BitmapDrawable(context.getResources(), b)));
+
         String identity = contact.getRingUsername();
         if (identity != null && !identity.equals(displayName)) {
             actionBar.setSubtitle(identity);
         }
+
+        mAdapter.setPhoto();
     }
 
     @Override
@@ -555,11 +583,6 @@
     }
 
     @Override
-    public void displayContactPhoto(final byte[] photo) {
-        mAdapter.setPhoto(photo);
-    }
-
-    @Override
     public void displayNumberSpinner(final Conversation conversation, final Uri number) {
         mNumberSpinner.setVisibility(View.VISIBLE);
         mNumberAdapter = new NumberAdapter(getActivity(),
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
index 69f5a09..22140ad 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
@@ -362,7 +362,8 @@
 
         ImageView photo = mNewContact.findViewById(R.id.photo);
 
-        Drawable contactPicture = AvatarFactory.getAvatar(
+        AvatarFactory.loadGlideAvatar(photo, contact);
+        /*Drawable contactPicture = AvatarFactory.getAvatar(
                 getActivity(),
                 contact.getPhoto(),
                 contact.getRingUsername(),
@@ -372,7 +373,7 @@
                 .load(contactPicture)
                 .apply(AvatarFactory.getGlideOptions(true, false))
                 //.transition(DrawableTransitionOptions.withCrossFade())
-                .into(photo);
+                .into(photo);*/
 
         mNewContact.setVisibility(View.VISIBLE);
     }
diff --git a/ring-android/app/src/main/java/cx/ring/navigation/AccountAdapter.java b/ring-android/app/src/main/java/cx/ring/navigation/AccountAdapter.java
index de1d9ec..a52063d 100644
--- a/ring-android/app/src/main/java/cx/ring/navigation/AccountAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/navigation/AccountAdapter.java
@@ -42,6 +42,7 @@
 import cx.ring.contacts.AvatarFactory;
 import cx.ring.model.Account;
 import cx.ring.utils.VCardUtils;
+import cx.ring.views.AvatarDrawable;
 import ezvcard.VCard;
 
 class AccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -183,17 +184,7 @@
 
         public void update(final Account account) {
             final Context context = itemView.getContext();
-            VCard vcard = account.getProfile();
-
-            Drawable accountPicture = AvatarFactory.getAvatar(context, vcard,
-                    account.getRegisteredName(),
-                    account.getUri());
-
-            Glide.with(context)
-                    .load(accountPicture)
-                    .apply(AvatarFactory.getGlideOptions(true, true))
-                    //.transition(DrawableTransitionOptions.withCrossFade())
-                    .into(photo);
+            photo.setImageDrawable(new AvatarDrawable(context, account));
 
             alias.setText(mRingNavigationPresenter.getAccountAlias(account));
             host.setText(mRingNavigationPresenter.getUri(account, context.getText(R.string.account_type_ip2ip)));
diff --git a/ring-android/app/src/main/java/cx/ring/navigation/RingNavigationFragment.java b/ring-android/app/src/main/java/cx/ring/navigation/RingNavigationFragment.java
index 55879e8..c7c5284 100644
--- a/ring-android/app/src/main/java/cx/ring/navigation/RingNavigationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/navigation/RingNavigationFragment.java
@@ -60,6 +60,8 @@
 import cx.ring.model.Account;
 import cx.ring.mvp.BaseFragment;
 import cx.ring.utils.BitmapUtils;
+import cx.ring.utils.VCardUtils;
+import cx.ring.views.AvatarDrawable;
 import ezvcard.VCard;
 import ezvcard.parameter.ImageType;
 import ezvcard.property.Photo;
@@ -236,12 +238,11 @@
             return;
         }
 
-        String username = account.getRegisteredName();
+        /*String username = account.getRegisteredName();
         String ringId = account.getUri();
-        Glide.with(getActivity())
-                .load(AvatarFactory.getAvatar(getActivity(), vcard, username, ringId))
-                .apply(AvatarFactory.getGlideOptions(true, true))
-                .into(mUserImage);
+        AvatarFactory.getGlideAvatar(getActivity(), Glide.with(this), vcard, username, ringId)
+                .into(mUserImage);*/
+        mUserImage.setImageDrawable(new AvatarDrawable(getActivity(), VCardUtils.readData(vcard), account.getRegisteredName(), account.getUri()));
     }
 
     public void updatePhoto(Uri uriImage) {
@@ -293,7 +294,7 @@
         final EditText editText = view.findViewById(R.id.user_name);
         editText.setText(presenter.getAlias(mSelectedAccount));
         mProfilePhoto = view.findViewById(R.id.profile_photo);
-        mProfilePhoto.setImageDrawable(mUserImage.getDrawable());
+        mProfilePhoto.setImageDrawable(new AvatarDrawable(inflater.getContext(), mSelectedAccount));
 
         ImageButton cameraView = view.findViewById(R.id.camera);
         cameraView.setOnClickListener(v -> presenter.cameraClicked());
diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
index 6bef8c9..c48023c 100644
--- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
@@ -53,6 +53,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
 
 import javax.inject.Inject;
 
@@ -371,12 +372,12 @@
                                     PendingIntent.FLAG_ONE_SHOT));
 
 
-            List<Photo> photos = contact.vcard == null ? null : contact.vcard.getPhotos();
+            /*List<Photo> photos = contact.vcard == null ? null : contact.vcard.getPhotos();
             byte[] data = null;
             if (photos != null && !photos.isEmpty()) {
                 data = photos.get(0).getData();
-            }
-            setContactPicture(data, contact.getRingUsername(), contact.getPrimaryNumber(), messageNotificationBuilder);
+            }*/
+            setContactPicture(contact, messageNotificationBuilder);
         } else {
             messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_REQUEST);
             boolean newRequest = false;
@@ -612,11 +613,16 @@
     }
 
     private void setContactPicture(CallContact contact, NotificationCompat.Builder messageNotificationBuilder) {
-        setContactPicture(contact.getPhoto(), contact.getUsername(),
-                contact.getPhones().get(0).getNumber().getHost(), messageNotificationBuilder);
+        int size = (int) (mContext.getResources().getDisplayMetrics().density * AvatarFactory.SIZE_NOTIF);
+        try {
+            messageNotificationBuilder.setLargeIcon(AvatarFactory.getBitmapGlideAvatar(mContext, contact)
+                    .submit(size, size)
+                    .get());
+        } catch (Exception e) {
+        }
     }
 
-    private void setContactPicture(byte[] photo, String username, String ringId, NotificationCompat.Builder messageNotificationBuilder) {
+    /*private void setContactPicture(byte[] photo, String username, String ringId, NotificationCompat.Builder messageNotificationBuilder) {
         Drawable contactPicture = AvatarFactory.getAvatar(mContext, photo, username, ringId);
 
         Bitmap contactBitmap = BitmapUtils.drawableToBitmap(contactPicture);
@@ -630,5 +636,5 @@
             return;
         }
         messageNotificationBuilder.setLargeIcon(circleBitmap);
-    }
+    }*/
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java b/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java
index 4025198..9c73b42 100644
--- a/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java
+++ b/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java
@@ -20,6 +20,7 @@
 
 import android.content.ComponentName;
 import android.content.IntentFilter;
+import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -28,8 +29,7 @@
 import android.service.chooser.ChooserTarget;
 import android.service.chooser.ChooserTargetService;
 import android.support.annotation.RequiresApi;
-
-import com.bumptech.glide.Glide;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,15 +47,19 @@
 
 @RequiresApi(api = Build.VERSION_CODES.M)
 public class RingChooserTargetService extends ChooserTargetService {
+
     @Inject
     @Singleton
     ConversationFacade conversationFacade;
 
+    private int targetSize;
+
     @Override
     public void onCreate() {
         super.onCreate();
         RingApplication.getInstance().startDaemon();
         RingApplication.getInstance().getRingInjectionComponent().inject(this);
+        targetSize = (int) (AvatarFactory.SIZE_NOTIF * getResources().getDisplayMetrics().density);
     }
 
     @Override
@@ -67,18 +71,10 @@
                         .getConversationsSubject()
                         .firstOrError()
                         .map(conversations -> {
-                            List<Future<Drawable>> futureIcons = new ArrayList<>(conversations.size());
+                            List<Future<Bitmap>> futureIcons = new ArrayList<>(conversations.size());
                             for (Conversation conversation : conversations) {
                                 CallContact contact = conversation.getContact();
-                                final BitmapDrawable contactPicture = AvatarFactory.getAvatar(
-                                        this,
-                                        contact.getPhoto(),
-                                        contact.getDisplayName(),
-                                        contact.getPrimaryNumber());
-                                futureIcons.add(Glide.with(this)
-                                        .load(contactPicture)
-                                        .apply(AvatarFactory.getGlideOptions(true, true))
-                                        .submit());
+                                futureIcons.add(AvatarFactory.getBitmapAvatar(this, contact, targetSize).toFuture());
                             }
                             int i=0;
                             List<ChooserTarget> choosers = new ArrayList<>(conversations.size());
@@ -87,8 +83,12 @@
                                 Bundle bundle = new Bundle();
                                 bundle.putString(ConversationFragment.KEY_ACCOUNT_ID, a.getAccountID());
                                 bundle.putString(ConversationFragment.KEY_CONTACT_RING_ID, contact.getPrimaryNumber());
-                                BitmapDrawable d = (BitmapDrawable) futureIcons.get(i).get();
-                                Icon icon = Icon.createWithBitmap(d.getBitmap());
+                                Icon icon = null;
+                                try {
+                                    icon = Icon.createWithBitmap(futureIcons.get(i).get());
+                                } catch (Exception e) {
+                                    Log.w("RingChooserService", "Failed to load icon", e);
+                                }
                                 ChooserTarget target = new ChooserTarget(contact.getDisplayName(), icon, 1.f-(i/(float)conversations.size()), componentName, bundle);
                                 choosers.add(target);
                                 i++;
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
index 68254b1..03902a5 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
@@ -214,7 +214,7 @@
 
         Glide.with(getActivity())
                 .load(model.getPhoto())
-                .apply(AvatarFactory.getGlideOptions(true, false))
+                .apply(AvatarFactory.getGlideOptions(true))
                 .transition(DrawableTransitionOptions.withCrossFade())
                 .into(getGuidanceStylist().getIconView());
     }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
index 241a3ea..080a635 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
@@ -52,6 +52,8 @@
 import cx.ring.navigation.RingNavigationView;
 import cx.ring.navigation.RingNavigationViewModel;
 import cx.ring.tv.camera.CustomCameraActivity;
+import cx.ring.utils.VCardUtils;
+import cx.ring.views.AvatarDrawable;
 import ezvcard.VCard;
 import ezvcard.parameter.ImageType;
 import ezvcard.property.Photo;
@@ -65,6 +67,7 @@
     private static final int CAMERA = 3;
 
     private List<GuidedAction> actions;
+    private int iconSize = -1;
 
     public static GuidedStepSupportFragment newInstance() {
         return new TVProfileEditingFragment();
@@ -113,6 +116,7 @@
     public void onViewCreated(View view, Bundle savedInstanceState) {
         ((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
         super.onViewCreated(view, savedInstanceState);
+        iconSize = (int) getResources().getDimension(R.dimen.tv_avatar_size);
     }
 
     @Override
@@ -188,22 +192,9 @@
         else
             getGuidanceStylist().getTitleView().setText(alias);
 
-        if (vcard == null || vcard.getPhotos().isEmpty()) {
-            getGuidanceStylist().getIconView().setImageDrawable(getActivity().getResources().getDrawable(R.drawable.ic_contact_picture_fallback));
-            return;
-        }
-
-        Drawable contactPicture = AvatarFactory.getAvatar(
-                getActivity(),
-                vcard.getPhotos().get(0).getData(),
-                account.getDisplayUsername(),
-                account.getUri());
-
-        Glide.with(getActivity())
-                .load(contactPicture)
-                .apply(AvatarFactory.getGlideOptions(true, false))
-                .transition(DrawableTransitionOptions.withCrossFade())
-                .into(getGuidanceStylist().getIconView());
+        AvatarDrawable avatar = new AvatarDrawable(getContext(), VCardUtils.readData(vcard), account.getUsername(), account == null ? null : account.getUsername(), true);
+        avatar.setInSize(iconSize);
+        getGuidanceStylist().getIconView().setImageDrawable(avatar);
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVRingAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVRingAccountCreationFragment.java
index d5f9f61..ec5d59f 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVRingAccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVRingAccountCreationFragment.java
@@ -102,7 +102,7 @@
 
         Glide.with(getActivity())
                 .load(ringAccountViewModel.getPhoto())
-                .apply(AvatarFactory.getGlideOptions(true, false))
+                .apply(AvatarFactory.getGlideOptions(true))
                 .transition(DrawableTransitionOptions.withCrossFade())
                 .into(getGuidanceStylist().getIconView());
     }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
index 604cc15..2a12969 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
@@ -321,7 +321,9 @@
             contactBubbleTxt.setText(username);
         }
 
-        mCompositeDisposable.add(Single.fromCallable(() -> Glide.with(getActivity())
+        AvatarFactory.loadGlideAvatar(contactBubbleView, contact);
+
+        /*mCompositeDisposable.add(Single.fromCallable(() -> Glide.with(getActivity())
                 .load(AvatarFactory.getAvatar(
                         getActivity(),
                         contact.getPhoto(),
@@ -332,7 +334,7 @@
                 .get())
                 .subscribeOn(Schedulers.computation())
                 .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(d -> contactBubbleView.setImageDrawable(d)));
+                .subscribe(d -> contactBubbleView.setImageDrawable(d)));*/
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
index d5723b2..fffcd08 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
@@ -21,8 +21,15 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.widget.ImageCardView;
 import android.view.ContextThemeWrapper;
+import android.widget.ImageView;
+
+import com.bumptech.glide.request.target.ImageViewTarget;
+import com.bumptech.glide.request.target.ViewTarget;
+import com.bumptech.glide.request.transition.Transition;
 
 import cx.ring.R;
 import cx.ring.contacts.AvatarFactory;
@@ -63,18 +70,11 @@
         }
 
         cardView.setBackgroundColor(cardView.getResources().getColor(R.color.color_primary_dark));
-        cardView.setMainImage(getCardImage(contact));
-    }
-
-    private Drawable getCardImage(ContactCard contact) {
-        String username = contact.getModel().getContact().getDisplayName();
-        if (username == null || username.isEmpty()) {
-            username = contact.getModel().getContact().getUsername();
-        }
-
-        return AvatarFactory.getAvatar(getContext(),
-                contact.getPhoto(),
-                username,
-                contact.getModel().getContact().getIds().get(0));
+        AvatarFactory.getGlideAvatar(getContext(), model).into(new ViewTarget<ImageCardView, Drawable>(cardView){
+            @Override
+            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
+                cardView.setMainImage(resource);
+            }
+        }.waitForLayout());
     }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contactrequest/TVContactRequestFragment.java b/ring-android/app/src/main/java/cx/ring/tv/contactrequest/TVContactRequestFragment.java
index 81f4553..f32b493 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/contactrequest/TVContactRequestFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/contactrequest/TVContactRequestFragment.java
@@ -133,15 +133,7 @@
     public void showRequest(TVListViewModel model) {
         final DetailsOverviewRow row = new DetailsOverviewRow(model);
 
-        Drawable contactPicture = AvatarFactory.getAvatar(getActivity(),
-                model.getContact().getPhoto(),
-                model.getContact().getDisplayName(),
-                model.getContact().getPrimaryNumber());
-
-        Glide.with(this)
-                .load(contactPicture)
-                .apply(AvatarFactory.getGlideOptions(false, true))
-                .into(new DetailsOverviewRowTarget(row, contactPicture));
+        AvatarFactory.getGlideAvatar(this, model.getContact()).into(new DetailsOverviewRowTarget(row));
 
         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
         adapter.set(ACTION_ACCEPT, new Action(ACTION_ACCEPT, getResources()
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
index 6ecda1d..88f65df 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
@@ -37,6 +37,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.bumptech.glide.Glide;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -244,42 +246,17 @@
     }
 
     @Override
-    public void displayAccountInfos(final String address, final RingNavigationViewModel viewModel) {
+    public void displayAccountInfos(final RingNavigationViewModel viewModel) {
         if (getActivity() == null) {
             Log.e(TAG, "displayAccountInfos: Not able to get activity");
             return;
         }
 
         VCard vcard = viewModel.getVcard();
-        if (vcard == null) {
-            Log.e(TAG, "displayAccountInfos: Not able to get vcard");
-            return;
-        }
-
-        FormattedName fn = vcard.getFormattedName();
-        String formattedName = fn == null ? null : fn.getValue();
-        if (formattedName != null && !formattedName.isEmpty()) {
-            titleView.setAlias(formattedName);
-            if (address != null) {
-                setTitle(address);
-            } else {
-                setTitle("");
-            }
-        } else {
-            titleView.setAlias(address);
-        }
-
-        byte[] data = null;
-        List<Photo> photos = vcard.getPhotos();
-        if (!photos.isEmpty() && photos.get(0) != null) {
-            data = photos.get(0).getData();
-        }
-        Drawable contactPicture = AvatarFactory.getAvatar(getActivity(),
-                data,
-                viewModel.getAccount().getDisplayUsername(),
-                address);
-
-        titleView.setCurrentAccountPhoto(contactPicture);
+        String registeredName = viewModel.getAccount().getRegisteredName();
+        String uri = viewModel.getAccount().getUri();
+        titleView.getLogoView().setVisibility(View.VISIBLE);
+        AvatarFactory.getGlideAvatar(getActivity(), Glide.with(this), vcard, registeredName, uri).into(titleView.getLogoView());
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
index a269fb3..ecf2646 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
@@ -189,7 +189,7 @@
                 .subscribe(accounts -> {
                     Account account = accounts.isEmpty() ? null : accounts.get(0);
                     RingNavigationViewModel viewModel = new RingNavigationViewModel(account, accounts);
-                    getView().displayAccountInfos(account == null ? null : account.getDisplayUri(), viewModel);
+                    getView().displayAccountInfos(viewModel);
                 }));
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
index fcad417..81fcf39 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
@@ -38,7 +38,7 @@
 
     void displayErrorToast(int error);
 
-    void displayAccountInfos(String address, RingNavigationViewModel viewModel);
+    void displayAccountInfos(RingNavigationViewModel viewModel);
 
     void showExportDialog(String pAccountID);
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java b/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
index 0826ac5..66f898b 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
@@ -110,13 +110,8 @@
         mLogoView.setVisibility(View.VISIBLE);
     }
 
-    public void setCurrentAccountPhoto(Drawable photo) {
-        Glide.with(getContext())
-                .load(photo)
-                .apply(AvatarFactory.getGlideOptions(true, true))
-                .into(mLogoView);
-
-        mLogoView.setVisibility(View.VISIBLE);
+    public ImageView getLogoView() {
+        return mLogoView;
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/tv/views/DetailsOverviewRowTarget.java b/ring-android/app/src/main/java/cx/ring/tv/views/DetailsOverviewRowTarget.java
index b3a9456..63a4758 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/views/DetailsOverviewRowTarget.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/views/DetailsOverviewRowTarget.java
@@ -27,36 +27,25 @@
 import android.util.Log;
 
 import com.bumptech.glide.request.target.BaseTarget;
+import com.bumptech.glide.request.target.SimpleTarget;
 import com.bumptech.glide.request.target.SizeReadyCallback;
 import com.bumptech.glide.request.transition.Transition;
 
-public class DetailsOverviewRowTarget extends BaseTarget<Drawable> {
+public class DetailsOverviewRowTarget extends SimpleTarget<Drawable> {
 
     private static final String TAG = DetailsOverviewRowTarget.class.getSimpleName();
     private DetailsOverviewRow detailsOverviewRow;
-    private Drawable drawable;
 
-    public DetailsOverviewRowTarget(DetailsOverviewRow detailsOverviewRow, Drawable drawable) {
-        if (detailsOverviewRow == null || drawable == null) {
+    public DetailsOverviewRowTarget(DetailsOverviewRow detailsOverviewRow) {
+        if (detailsOverviewRow == null) {
             Log.d(TAG, "DetailsOverviewRowTarget: invalid parameter");
             return;
         }
         this.detailsOverviewRow = detailsOverviewRow;
-        this.drawable = drawable;
     }
 
     @Override
     public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
         this.detailsOverviewRow.setImageDrawable(resource);
     }
-
-    @Override
-    public void getSize(@NonNull SizeReadyCallback cb) {
-        cb.onSizeReady(drawable.getMinimumWidth(), drawable.getMinimumHeight());
-    }
-
-    @Override
-    public void removeCallback(@NonNull SizeReadyCallback cb) {
-        // Do nothing, we never retain a reference to the callback
-    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java
new file mode 100644
index 0000000..09b4c9f
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.views;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import cx.ring.R;
+import cx.ring.model.Account;
+import cx.ring.model.CallContact;
+import cx.ring.utils.HashUtils;
+import cx.ring.utils.Tuple;
+import cx.ring.utils.VCardUtils;
+
+import android.media.ThumbnailUtils;
+import android.support.annotation.NonNull;
+import android.support.graphics.drawable.VectorDrawableCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+
+public class AvatarDrawable extends Drawable {
+    private static final int SIZE_AB = 36;
+    private static final float DEFAULT_TEXT_SIZE_PERCENTAGE = 0.5f;
+    private static final int PLACEHOLDER_ICON = R.drawable.baseline_account_circle_24;
+
+    private static final int[] contactColors = {
+            R.color.red_500, R.color.pink_500,
+            R.color.purple_500, R.color.deep_purple_500,
+            R.color.indigo_500, R.color.blue_500,
+            R.color.cyan_500, R.color.teal_500,
+            R.color.green_500, R.color.light_green_500,
+            R.color.grey_500, R.color.lime_500,
+            R.color.amber_500, R.color.deep_orange_500,
+            R.color.brown_500, R.color.blue_grey_500
+    };
+
+    private final boolean cropCircle;
+    private boolean update = true;
+    private int inSize = -1;
+
+    private final int minSize;
+    private Bitmap workspace;
+    private final Bitmap bitmap;
+    private VectorDrawableCompat placeholder;
+    private final RectF backgroundBounds = new RectF();
+    private final String avatarText;
+    private float textStartXPoint;
+    private float textStartYPoint;
+    private int color;
+
+    private final Paint clipPaint = new Paint();
+    private final Paint textPaint = new Paint();
+    private static final Paint drawPaint = new Paint();
+    static {
+        drawPaint.setAntiAlias(true);
+        drawPaint.setFilterBitmap(true);
+    }
+
+    public AvatarDrawable(Context context, CallContact contact) {
+        this(context, contact.getPhoto(), contact.getProfileName(), contact.getUsername(), contact.getPrimaryNumber(), true);
+    }
+    public AvatarDrawable(Context context, CallContact contact, boolean crop) {
+        this(context, contact.getPhoto(), contact.getProfileName(), contact.getUsername(), contact.getPrimaryNumber(), crop);
+    }
+    public AvatarDrawable(Context context, Account account, boolean crop) {
+        this(context, VCardUtils.readData(account.getProfile()), account.getRegisteredName(), account.getUri(), crop);
+    }
+    public AvatarDrawable(Context context, Account account) {
+        this(context, VCardUtils.readData(account.getProfile()), account.getRegisteredName(), account.getUri(), true);
+    }
+    public AvatarDrawable(Context context, byte[] photo, String profileName, String username, String id, boolean crop) {
+        this(context, photo, TextUtils.isEmpty(profileName) ? username : profileName, id, crop);
+    }
+    public AvatarDrawable(Context context, Bitmap photo, String profileName, String username, String id, boolean crop) {
+        this(context, photo, TextUtils.isEmpty(profileName) ? username : profileName, id, crop);
+    }
+    public AvatarDrawable(Context context, Tuple<String, byte[]> data, String registeredName, String uri, boolean crop) {
+        this(context, data.second, data.first, registeredName, uri, crop);
+    }
+    public AvatarDrawable(Context context, Tuple<String, byte[]> data, String registeredName, String uri) {
+        this(context, data.second, data.first, registeredName, uri, true);
+    }
+    public AvatarDrawable(Context context, byte[] photo, String name, String id, boolean crop) {
+        this(context, photo == null ? null : getBitmap(photo), name, id, crop);
+    }
+
+    private static Bitmap getBitmap(byte[] photo) {
+        Bitmap source = BitmapFactory.decodeByteArray(photo, 0, photo.length);
+        int d = Math.min(source.getWidth(), source.getHeight());
+        return ThumbnailUtils.extractThumbnail(source, d, d);
+    }
+
+    public AvatarDrawable(Context context, Bitmap photo, String name, String id, boolean crop) {
+        Log.w("AvatarDrawable", photo + " " + name + " " + id);
+        cropCircle = crop;
+        Resources res = context.getResources();
+        minSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SIZE_AB, res.getDisplayMetrics());
+        clipPaint.setAntiAlias(true);
+        if (photo != null) {
+            avatarText = null;
+            bitmap = photo;
+        } else {
+            bitmap = null;
+            avatarText = convertNameToAvatarText(name);
+            color = res.getColor(generateAvatarColor(id));
+            if (avatarText == null) {
+                placeholder = VectorDrawableCompat.create(context.getResources(), PLACEHOLDER_ICON, context.getTheme());
+            } else {
+                textPaint.setColor(Color.WHITE);
+                textPaint.setTypeface(Typeface.SANS_SERIF);
+            }
+        }
+        textPaint.setAntiAlias(true);
+    }
+
+    @Override
+    public void draw(@NonNull Canvas finalCanvas) {
+        if (workspace == null)
+            return;
+        if (update) {
+            drawActual(new Canvas(workspace));
+            update = false;
+        }
+        if (cropCircle) {
+            int d = Math.min(getBounds().width(), getBounds().height());
+            int r = d / 2;
+            finalCanvas.drawCircle(getBounds().centerX(), getBounds().centerY(), r, clipPaint);
+        } else {
+            finalCanvas.drawBitmap(workspace, null, getBounds(), drawPaint);
+        }
+    }
+
+    private void drawActual(@NonNull Canvas canvas) {
+        if (bitmap != null) {
+            canvas.drawBitmap(bitmap, null, backgroundBounds, drawPaint);
+        } else if (placeholder == null) {
+            canvas.drawColor(color);
+            canvas.drawText(avatarText, textStartXPoint, textStartYPoint, textPaint);
+        } else {
+            canvas.drawColor(0xffffffff);
+            canvas.save();
+            canvas.scale(1.2f, 1.2f, getBounds().centerX(), getBounds().centerY());
+            placeholder.setTint(color);
+            placeholder.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        //Log.w("AvatarDrawable", this + "onBoundsChange " + bounds.width() + " " + bounds.height());
+        setAvatarTextValues();
+        int d = Math.min(bounds.width(), bounds.height());
+        if (placeholder != null) {
+            int cx = (bounds.width()-d)/2;
+            int cy = (bounds.height()-d)/2;
+            placeholder.setBounds(cx, cy, cx + d, cy + d);
+        }
+        if (cropCircle) {
+            if (bitmap != null) {
+                int r = d / 2;
+                int cx = bounds.centerX();
+                int cy = bounds.centerY();
+                backgroundBounds.set(cx - r, cy - r, cx + r, cy + r);
+            }
+            if (d > 0) {
+                workspace = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
+                clipPaint.setShader(new BitmapShader(workspace, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
+            } else {
+                clipPaint.setShader(null);
+                workspace.recycle();
+                workspace = null;
+            }
+        } else {
+            if (bitmap != null) {
+                int a = bitmap.getWidth() * getBounds().height();
+                int b = bitmap.getHeight() * getBounds().width();
+                int w;
+                int h;
+                if (a < b) {
+                    w = Math.max(bitmap.getWidth(), getBounds().width());
+                    h  = (w * bitmap.getHeight())/bitmap.getWidth();
+                } else {
+                    h = Math.max(bitmap.getHeight(), getBounds().height());
+                    w  = (h * bitmap.getWidth())/bitmap.getHeight();
+                }
+                backgroundBounds.set(0, 0, w, h);
+            } else {
+                backgroundBounds.set(getBounds());
+            }
+            workspace = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Bitmap.Config.ARGB_8888);
+        }
+        update = true;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (placeholder != null) {
+            placeholder.setAlpha(alpha);
+        } else {
+            textPaint.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        if (placeholder != null) {
+            placeholder.setColorFilter(colorFilter);
+        } else {
+            textPaint.setColorFilter(colorFilter);
+        }
+    }
+
+    @Override
+    public int getMinimumWidth() {
+        return minSize;
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        return minSize;
+    }
+
+    public void setInSize(int s) {
+        inSize = s;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return inSize;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return inSize;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    private void setAvatarTextValues() {
+        if (avatarText != null) {
+            textPaint.setTextSize(getBounds().height() * DEFAULT_TEXT_SIZE_PERCENTAGE);
+            textStartXPoint = calculateTextStartXPoint();
+            textStartYPoint = calculateTextStartYPoint();
+        }
+    }
+
+    private float calculateTextStartXPoint() {
+        float stringWidth = textPaint.measureText(avatarText);
+        return (getBounds().width() / 2f) - (stringWidth / 2f);
+    }
+
+    private float calculateTextStartYPoint() {
+        return (getBounds().height() / 2f) - ((textPaint.ascent() + textPaint.descent()) / 2f);
+    }
+
+    private String convertNameToAvatarText(String name) {
+        return TextUtils.isEmpty(name) ? null : name.substring(0, 1).toUpperCase();
+    }
+
+    private static int generateAvatarColor(String id) {
+        if (id == null) {
+            return R.color.grey_500;
+        }
+
+        String md5 = HashUtils.md5(id);
+        if (md5 == null) {
+            return R.color.grey_500;
+        }
+        int colorIndex = Integer.parseInt(md5.charAt(0) + "", 16);
+        return contactColors[colorIndex % contactColors.length];
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/drawable/baseline_account_circle_24.xml b/ring-android/app/src/main/res/drawable/baseline_account_circle_24.xml
new file mode 100755
index 0000000..4a91951
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_account_circle_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/layout/activity_conversation.xml b/ring-android/app/src/main/res/layout/activity_conversation.xml
index 8840c64..be163ed 100644
--- a/ring-android/app/src/main/res/layout/activity_conversation.xml
+++ b/ring-android/app/src/main/res/layout/activity_conversation.xml
@@ -18,15 +18,16 @@
         android:minHeight="?attr/actionBarSize"
         android:popupTheme="@style/Theme.AppCompat.Light.NoActionBar"
         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
-        android:titleTextAppearance="@style/ToolbarTitle"
         app:contentInsetStart="72dp"
         app:elevation="4dp"
-        app:titleTextAppearance="@style/ToolbarTitle" />
+        app:subtitleTextAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle"
+        app:titleMarginStart="24dp"
+        app:titleTextAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" />
 
     <FrameLayout
         android:id="@+id/main_frame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:layout_below="@+id/main_toolbar" />
+        android:layout_below="@id/main_toolbar"
+        android:layout_alignParentStart="true" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/dimens.xml b/ring-android/app/src/main/res/values/dimens.xml
index 341f823..8b62b48 100644
--- a/ring-android/app/src/main/res/values/dimens.xml
+++ b/ring-android/app/src/main/res/values/dimens.xml
@@ -49,4 +49,5 @@
     <dimen name="search_image_card_height">90dp</dimen>
 
     <dimen name="share_preview_height">120dp</dimen>
+    <dimen name="tv_avatar_size">320dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/styles.xml b/ring-android/app/src/main/res/values/styles.xml
index ce4af49..9a869a9 100644
--- a/ring-android/app/src/main/res/values/styles.xml
+++ b/ring-android/app/src/main/res/values/styles.xml
@@ -28,7 +28,6 @@
     </style>
 
     <style name="ToolbarTitle" parent="@style/TextAppearance.Widget.AppCompat.Toolbar.Title">
-        <item name="android:textSize">20sp</item>
     </style>
 
     <style name="MenuHeader" parent="Theme.AppCompat.Light.NoActionBar">