tv: add contact view

Change-Id: Ib6d45e2c14073b0c980365eba4f028137e570d1e
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 5d266ca..03ec089 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -330,6 +330,10 @@
             android:name=".tv.contactrequest.TVContactRequestActivity"
             android:theme="@style/Theme.Leanback.Details" />
         <activity
+            android:name=".tv.contact.TVContactActivity"
+            android:theme="@style/Theme.Leanback.Details" />
+
+        <activity
             android:name=".tv.account.TVSettingsActivity"
             android:theme="@style/LeanbackPreferences" />
 
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
index 8099d43..4a99f00 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
@@ -76,6 +76,7 @@
 import cx.ring.tv.account.TVSettingsFragment;
 import cx.ring.tv.call.TVCallActivity;
 import cx.ring.tv.call.TVCallFragment;
+import cx.ring.tv.contact.TVContactFragment;
 import cx.ring.tv.contactrequest.TVContactRequestFragment;
 import cx.ring.tv.main.MainFragment;
 import cx.ring.tv.search.RingSearchFragment;
@@ -193,6 +194,8 @@
 
     void inject(TVContactRequestFragment fragment);
 
+    void inject(TVContactFragment fragment);
+
     void inject(TVSettingsFragment tvSettingsFragment);
 
     void inject(TVSettingsFragment.PrefsFragment prefsFragment);
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 987ae36..9e0df9e 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
@@ -104,8 +104,8 @@
 
     public static final int REQ_ADD_CONTACT = 42;
 
-    public static final String KEY_CONTACT_RING_ID = BuildConfig.APPLICATION_ID + "CONTACT_RING_ID";
-    public static final String KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + "ACCOUNT_ID";
+    public static final String KEY_CONTACT_RING_ID = BuildConfig.APPLICATION_ID + ".CONTACT_RING_ID";
+    public static final String KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + ".ACCOUNT_ID";
     public static final String KEY_PREFERENCE_PENDING_MESSAGE = "pendingMessage";
     public static final String KEY_PREFERENCE_CONVERSATION_COLOR = "color";
 
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
index bab8d92..be443d8 100644
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
@@ -584,7 +584,7 @@
             boolean incomingCall = newState == SipCall.State.RINGING && call.isIncoming();
             if (incomingCall) {
                 Bundle extras = new Bundle();
-                extras.putString("account", mAccountService.getCurrentAccount().getAccountID());
+                extras.putString(ConversationFragment.KEY_ACCOUNT_ID, mAccountService.getCurrentAccount().getAccountID());
                 extras.putString(NotificationService.KEY_CALL_ID, call.getCallId());
                 startActivity(new Intent(Intent.ACTION_VIEW)
                         .putExtras(extras)
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
index 5819abe..05617c3 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
@@ -22,6 +22,7 @@
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
+import android.media.AudioManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -32,6 +33,7 @@
 import cx.ring.R;
 import cx.ring.application.RingApplication;
 import cx.ring.call.CallView;
+import cx.ring.fragments.ConversationFragment;
 import cx.ring.services.NotificationService;
 import cx.ring.utils.Log;
 
@@ -54,14 +56,15 @@
                     WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
         }
         setContentView(R.layout.tv_activity_call);
+        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
 
         // dependency injection
         RingApplication.getInstance().getRingInjectionComponent().inject(this);
         RingApplication.getInstance().startDaemon();
 
         boolean audioOnly = false;
-        String accountId = getIntent().getStringExtra("account");
-        String ringId = getIntent().getStringExtra("ringId");
+        String accountId = getIntent().getStringExtra(ConversationFragment.KEY_ACCOUNT_ID);
+        String ringId = getIntent().getStringExtra(ConversationFragment.KEY_CONTACT_RING_ID);
 
         FragmentManager fragmentManager = getFragmentManager();
         FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java
new file mode 100644
index 0000000..bb88755
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java
@@ -0,0 +1,39 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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.tv.contact;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import cx.ring.R;
+
+public class TVContactActivity extends FragmentActivity {
+    public static final String SHARED_ELEMENT_NAME = "photo";
+    public static final String CONTACT_REQUEST = "ContactRequest";
+
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.tv_frag_contact);
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java
new file mode 100644
index 0000000..3c23b89
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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.tv.contact;
+
+import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
+import cx.ring.tv.model.TVListViewModel;
+
+public class TVContactDetailPresenter extends AbstractDetailsDescriptionPresenter {
+
+    @Override
+    protected void onBindDescription(ViewHolder viewHolder, Object item) {
+        TVListViewModel viewModel = (TVListViewModel) item;
+        if (viewModel != null) {
+            String id = viewModel.getContact().getRingUsername();
+            String displayName = viewModel.getContact().getDisplayName();
+            viewHolder.getTitle().setText(displayName);
+            if (!displayName.equals(id))
+                viewHolder.getSubtitle().setText(id);
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java
new file mode 100644
index 0000000..a7d6227
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java
@@ -0,0 +1,147 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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
+ *  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.tv.contact;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+
+import androidx.core.content.ContextCompat;
+import androidx.leanback.app.BackgroundManager;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.BrowseFrameLayout;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.DetailsOverviewLogoPresenter;
+import androidx.leanback.widget.DetailsOverviewRow;
+import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
+import androidx.leanback.widget.FullWidthDetailsOverviewSharedElementHelper;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.ListRowPresenter;
+import cx.ring.R;
+import cx.ring.application.RingApplication;
+import cx.ring.fragments.ConversationFragment;
+import cx.ring.model.Uri;
+import cx.ring.tv.call.TVCallActivity;
+import cx.ring.tv.main.BaseDetailFragment;
+import cx.ring.tv.model.TVListViewModel;
+import cx.ring.views.AvatarDrawable;
+
+public class TVContactFragment extends BaseDetailFragment<TVContactPresenter> implements TVContactView {
+
+    private static final int ACTION_CALL = 0;
+    private static final int ACTION_DELETE = 1;
+
+    private Uri mContactUri;
+    private ArrayObjectAdapter mAdapter;
+    private int iconSize = -1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        ((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
+        super.onCreate(savedInstanceState);
+        mContactUri = (Uri) getActivity().getIntent().getSerializableExtra(TVContactActivity.CONTACT_REQUEST);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        BrowseFrameLayout layout = (BrowseFrameLayout) view;
+
+        // Override down navigation as we do not use it in this screen
+        // Only the detailPresenter will be displayed
+        layout.setOnDispatchKeyListener((v, keyCode, event) -> event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN);
+        prepareBackgroundManager();
+        if (mContactUri != null) {
+            setupAdapter();
+        }
+        Resources res = getResources();
+        iconSize = res.getDimensionPixelSize(R.dimen.tv_avatar_size);
+        presenter.setContact(mContactUri);
+    }
+
+    private void prepareBackgroundManager() {
+        BackgroundManager mBackgroundManager = BackgroundManager.getInstance(getActivity());
+        mBackgroundManager.attach(getActivity().getWindow());
+    }
+
+    private void setupAdapter() {
+        // Set detail background and style.
+        FullWidthDetailsOverviewRowPresenter detailsPresenter =
+                new FullWidthDetailsOverviewRowPresenter(
+                        new TVContactDetailPresenter(),
+                        new DetailsOverviewLogoPresenter());
+
+        detailsPresenter.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.color_primary_dark));
+        detailsPresenter.setInitialState(FullWidthDetailsOverviewRowPresenter.STATE_HALF);
+
+        // Hook up transition element.
+        FullWidthDetailsOverviewSharedElementHelper mHelper = new FullWidthDetailsOverviewSharedElementHelper();
+        mHelper.setSharedElementEnterTransition(getActivity(), TVContactActivity.SHARED_ELEMENT_NAME);
+        detailsPresenter.setListener(mHelper);
+        detailsPresenter.setParticipatingEntranceTransition(false);
+        prepareEntranceTransition();
+
+        detailsPresenter.setOnActionClickedListener(action -> {
+            if (action.getId() == ACTION_CALL) {
+                presenter.contactClicked(mContactUri);
+            } else if (action.getId() == ACTION_DELETE) {
+                presenter.removeContact(mContactUri);
+            }
+        });
+
+        ClassPresenterSelector mPresenterSelector = new ClassPresenterSelector();
+        mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);
+        mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+        mAdapter = new ArrayObjectAdapter(mPresenterSelector);
+        setAdapter(mAdapter);
+    }
+
+    public void showContact(TVListViewModel model) {
+        final DetailsOverviewRow row = new DetailsOverviewRow(model);
+        AvatarDrawable avatar = new AvatarDrawable(getActivity(), model.getContact(), false);
+        avatar.setInSize(iconSize);
+        row.setImageDrawable(avatar);
+
+        ArrayObjectAdapter adapter = new ArrayObjectAdapter();
+        adapter.add(ACTION_CALL, new Action(ACTION_CALL, getResources().getString(R.string.ab_action_video_call)));
+        adapter.add(ACTION_DELETE, new Action(ACTION_DELETE, getResources().getString(R.string.conversation_action_remove_this)));
+        row.setActionsAdapter(adapter);
+
+        mAdapter.add(row);
+    }
+
+    @Override
+    public void callContact(String accountID, Uri uri) {
+        Intent intent = new Intent(getActivity(), TVCallActivity.class);
+        intent.putExtra(ConversationFragment.KEY_ACCOUNT_ID, accountID);
+        intent.putExtra(ConversationFragment.KEY_CONTACT_RING_ID, uri.getRawUriString());
+        getActivity().startActivity(intent, null);
+    }
+
+    @Override
+    public void finishView() {
+        getActivity().finish();
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java
new file mode 100644
index 0000000..a6af67f
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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
+ *  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.tv.contact;
+
+import javax.inject.Inject;
+
+import cx.ring.facades.ConversationFacade;
+import cx.ring.model.Account;
+import cx.ring.model.RingError;
+import cx.ring.model.Uri;
+import cx.ring.mvp.RootPresenter;
+import cx.ring.services.AccountService;
+import cx.ring.services.HardwareService;
+import cx.ring.tv.model.TVListViewModel;
+import io.reactivex.Scheduler;
+
+public class TVContactPresenter extends RootPresenter<TVContactView> {
+
+    private final AccountService mAccountService;
+    private final ConversationFacade mConversationService;
+    private final HardwareService mHardwareService;
+    private final Scheduler mUiScheduler;
+
+    @Inject
+    public TVContactPresenter(AccountService accountService,
+                              ConversationFacade conversationService,
+                              HardwareService hardwareService,
+                              Scheduler uiScheduler) {
+        mAccountService = accountService;
+        mConversationService = conversationService;
+        mHardwareService = hardwareService;
+        mUiScheduler = uiScheduler;
+    }
+
+    public void setContact(Uri uri) {
+        mCompositeDisposable.add(mConversationService
+                .getCurrentAccountSubject()
+                .map(a -> new TVListViewModel(a.getByUri(uri).getContact()))
+                .observeOn(mUiScheduler)
+                .subscribe(c -> getView().showContact(c)));
+    }
+
+    public void removeContact(Uri viewModel) {
+        String accountId = mAccountService.getCurrentAccount().getAccountID();
+        mConversationService.removeConversation(accountId, viewModel).subscribe();
+        getView().finishView();
+    }
+
+    public void contactClicked(Uri uri) {
+        if (!mHardwareService.isVideoAvailable() && !mHardwareService.hasMicrophone()) {
+            getView().displayErrorToast(RingError.NO_INPUT);
+            return;
+        }
+
+        Account account = mAccountService.getCurrentAccount();
+        getView().callContact(account.getAccountID(), uri);
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java
new file mode 100644
index 0000000..e0dcda7
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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
+ *  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.tv.contact;
+
+import cx.ring.model.Uri;
+import cx.ring.mvp.BaseView;
+import cx.ring.tv.model.TVListViewModel;
+
+public interface TVContactView extends BaseView {
+     void showContact(TVListViewModel model);
+     void callContact(String accountID, Uri uri);
+     void finishView();
+}
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 48c6ff8..895f3e5 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
@@ -33,15 +33,11 @@
 import androidx.leanback.widget.FullWidthDetailsOverviewSharedElementHelper;
 import androidx.leanback.widget.ListRow;
 import androidx.leanback.widget.ListRowPresenter;
-import androidx.leanback.widget.Presenter;
 import androidx.leanback.widget.SparseArrayObjectAdapter;
 import androidx.core.content.ContextCompat;
 
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
 
 import cx.ring.R;
 import cx.ring.application.RingApplication;
@@ -52,15 +48,13 @@
 
 public class TVContactRequestFragment extends BaseDetailFragment<TVContactRequestPresenter> implements TVContactRequestView {
 
-    private static final int ACTION_ACCEPT = 1;
-    private static final int ACTION_REFUSE = 2;
-    private static final int ACTION_BLOCK = 3;
+    private static final int ACTION_ACCEPT = 0;
+    private static final int ACTION_REFUSE = 1;
+    private static final int ACTION_BLOCK = 2;
 
     private Uri mSelectedContactRequest;
     private ArrayObjectAdapter mAdapter;
     private int iconSize = -1;
-    private int imageCardWidth = -1;
-    private int imageCardHeight = -1;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -86,8 +80,6 @@
         }
         Resources res = getResources();
         iconSize = res.getDimensionPixelSize(R.dimen.tv_avatar_size);
-        imageCardWidth = res.getDimensionPixelSize(R.dimen.default_image_card_width);
-        imageCardHeight = res.getDimensionPixelSize(R.dimen.default_image_card_height);
         presenter.setContact(mSelectedContactRequest);
     }
 
@@ -98,9 +90,9 @@
 
     private void setupAdapter() {
         // Set detail background and style.
-        FullWidthDetailsOverviewRowPresenter detailsPresenter =
-                new FullWidthDetailsOverviewRowPresenter(new TVContactRequestDetailPresenter(),
-                        new LogoPresenter(imageCardWidth, imageCardHeight));
+        FullWidthDetailsOverviewRowPresenter detailsPresenter = new FullWidthDetailsOverviewRowPresenter(
+                new TVContactRequestDetailPresenter(),
+                new DetailsOverviewLogoPresenter());
 
         detailsPresenter.setBackgroundColor(
                 ContextCompat.getColor(getActivity(), R.color.color_primary_dark));
@@ -108,8 +100,7 @@
 
         // Hook up transition element.
         FullWidthDetailsOverviewSharedElementHelper mHelper = new FullWidthDetailsOverviewSharedElementHelper();
-        mHelper.setSharedElementEnterTransition(getActivity(),
-                TVContactRequestActivity.SHARED_ELEMENT_NAME);
+        mHelper.setSharedElementEnterTransition(getActivity(), TVContactRequestActivity.SHARED_ELEMENT_NAME);
         detailsPresenter.setListener(mHelper);
         detailsPresenter.setParticipatingEntranceTransition(false);
         prepareEntranceTransition();
@@ -136,6 +127,7 @@
         AvatarDrawable avatar = new AvatarDrawable(getActivity(), model.getContact(), false);
         avatar.setInSize(iconSize);
         row.setImageDrawable(avatar);
+        row.setImageScaleUpAllowed(false);
 
         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
         adapter.set(ACTION_ACCEPT, new Action(ACTION_ACCEPT, getResources()
@@ -151,32 +143,4 @@
     public void finishView() {
         getActivity().finish();
     }
-
-    static class LogoPresenter extends DetailsOverviewLogoPresenter {
-        private final int lw, lh;
-        LogoPresenter(int w, int h) {
-            lw = w;
-            lh = h;
-        }
-
-        @Override
-        public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
-            ImageView imageView = (ImageView) LayoutInflater.from(parent.getContext())
-                    .inflate(R.layout.lb_fullwidth_details_overview_logo, parent, false);
-            imageView.setLayoutParams(new ViewGroup.MarginLayoutParams(lw, lh));
-            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
-            return new ViewHolder(imageView);
-        }
-
-        @Override
-        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
-            DetailsOverviewRow row = (DetailsOverviewRow) item;
-            ImageView imageView = ((ImageView) viewHolder.view);
-            imageView.setImageDrawable(row.getImageDrawable());
-            if (isBoundToImage((ViewHolder) viewHolder, row)) {
-                LogoPresenter.ViewHolder vh = (LogoPresenter.ViewHolder) viewHolder;
-                vh.getParentPresenter().notifyOnBindLogo(vh.getParentViewHolder());
-            }
-        }
-    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java
index 315aa78..796c39f 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java
@@ -20,18 +20,18 @@
 package cx.ring.tv.main;
 
 import android.os.Bundle;
-import androidx.leanback.app.DetailsFragment;
 import android.view.View;
 import android.widget.Toast;
 
 import javax.inject.Inject;
 
+import androidx.leanback.app.DetailsSupportFragment;
 import cx.ring.R;
 import cx.ring.model.RingError;
 import cx.ring.mvp.BaseView;
 import cx.ring.mvp.RootPresenter;
 
-public class BaseDetailFragment<T extends RootPresenter> extends DetailsFragment implements BaseView {
+public class BaseDetailFragment<T extends RootPresenter> extends DetailsSupportFragment implements BaseView {
 
     protected static final String TAG = BaseBrowseFragment.class.getSimpleName();
 
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 d791cb7..c4a15fd 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
@@ -43,6 +43,7 @@
 
 import cx.ring.R;
 import cx.ring.application.RingApplication;
+import cx.ring.fragments.ConversationFragment;
 import cx.ring.model.Account;
 import cx.ring.navigation.RingNavigationViewModel;
 import cx.ring.services.VCardServiceImpl;
@@ -59,6 +60,7 @@
 import cx.ring.tv.cards.contacts.ContactCard;
 import cx.ring.tv.cards.iconcards.IconCard;
 import cx.ring.tv.cards.iconcards.IconCardHelper;
+import cx.ring.tv.contact.TVContactActivity;
 import cx.ring.tv.contactrequest.TVContactRequestActivity;
 import cx.ring.tv.model.TVListViewModel;
 import cx.ring.tv.search.SearchActivity;
@@ -239,10 +241,10 @@
     }
 
     @Override
-    public void callContact(String accountID, String ringID) {
+    public void callContact(String accountID, String number) {
         Intent intent = new Intent(getActivity(), TVCallActivity.class);
-        intent.putExtra("account", accountID);
-        intent.putExtra("ringId", ringID);
+        intent.putExtra(ConversationFragment.KEY_ACCOUNT_ID, accountID);
+        intent.putExtra(ConversationFragment.KEY_CONTACT_RING_ID, number);
         getActivity().startActivity(intent, null);
     }
 
@@ -316,7 +318,13 @@
                             TVContactRequestActivity.SHARED_ELEMENT_NAME).toBundle();
                     getActivity().startActivity(intent, bundle);
                 } else {
-                    presenter.contactClicked(model);
+                    Intent intent = new Intent(getActivity(), TVContactActivity.class);
+                    intent.putExtra(TVContactActivity.CONTACT_REQUEST, model.getContact().getPrimaryUri());
+
+                    Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
+                            ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                            TVContactRequestActivity.SHARED_ELEMENT_NAME).toBundle();
+                    getActivity().startActivity(intent, bundle);
                 }
             } else if (item instanceof IconCard) {
                 IconCard card = (IconCard) item;
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchFragment.java b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchFragment.java
index 4dcd1be..5554981 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchFragment.java
@@ -40,6 +40,7 @@
 import butterknife.Unbinder;
 import cx.ring.R;
 import cx.ring.application.RingApplication;
+import cx.ring.fragments.ConversationFragment;
 import cx.ring.model.CallContact;
 import cx.ring.tv.call.TVCallActivity;
 import cx.ring.tv.cards.Card;
@@ -128,8 +129,8 @@
     @Override
     public void startCall(String accountID, String number) {
         Intent intent = new Intent(getActivity(), TVCallActivity.class);
-        intent.putExtra("account", accountID);
-        intent.putExtra("ringId", number);
+        intent.putExtra(ConversationFragment.KEY_ACCOUNT_ID, accountID);
+        intent.putExtra(ConversationFragment.KEY_CONTACT_RING_ID, number);
         getActivity().startActivity(intent, null);
         getActivity().finish();
     }
diff --git a/ring-android/app/src/main/res/layout/tv_frag_contact.xml b/ring-android/app/src/main/res/layout/tv_frag_contact.xml
new file mode 100644
index 0000000..ba46381
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/tv_frag_contact.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/details_fragment"
+    android:name="cx.ring.tv.contact.TVContactFragment"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
\ No newline at end of file