AndroidTv: add presence

This patch adds a small green icon on online contacts. Easy to custom
with styles.

Change-Id: Ic8543d489301c44e4ce1ab851e6084dca4b5783d
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java b/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
index 5cd22df..6090f62 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
@@ -129,7 +129,9 @@
         ACCOUNT_ADD_DEVICE,
         ABOUT_LICENCES,
         CONTACT,
+        CONTACT_ONLINE,
         CONTACT_WITH_USERNAME,
+        CONTACT_WITH_USERNAME_ONLINE,
     }
 
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java b/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
index 4ce4824..3a3c9e3 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
@@ -58,9 +58,15 @@
                 case CONTACT:
                     presenter = new ContactCardPresenter(mContext, R.style.ContactCardTheme);
                     break;
+                case CONTACT_ONLINE:
+                    presenter = new ContactCardPresenter(mContext, R.style.ContactCardOnlineTheme);
+                    break;
                 case CONTACT_WITH_USERNAME:
                     presenter = new ContactCardPresenter(mContext, R.style.ContactCompleteCardTheme);
                     break;
+                case CONTACT_WITH_USERNAME_ONLINE:
+                    presenter = new ContactCardPresenter(mContext, R.style.ContactCompleteCardOnlineTheme);
+                    break;
                 default:
                     throw new InvalidParameterException("Uncatched card type");
             }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java
index ea292a1..d8059dd 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java
@@ -19,15 +19,19 @@
  */
 package cx.ring.tv.cards.contacts;
 
+import java.util.Arrays;
+
 import cx.ring.model.CallContact;
+import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.tv.cards.Card;
 
 public class ContactCard extends Card {
-    CallContact mCallContact = null;
+    SmartListViewModel mModel = null;
+    CallContact mContact = null;
     private byte[] mPhoto = null;
 
     public ContactCard(CallContact pCallContact, Type type) {
-        mCallContact = pCallContact;
+        mContact = pCallContact;
         setId(pCallContact.getId());
         setTitle(pCallContact.getDisplayName());
         setDescription(pCallContact.getRingUsername());
@@ -37,26 +41,60 @@
         setType(type);
     }
 
-    public ContactCard(CallContact pCallContact) {
-        mCallContact = pCallContact;
-        setId(pCallContact.getId());
-        setTitle(pCallContact.getDisplayName());
-        setDescription(pCallContact.getRingUsername());
-        if (pCallContact.getPhoto() != null) {
-            mPhoto = pCallContact.getPhoto();
+    public ContactCard(SmartListViewModel model) {
+        mModel = model;
+        setTitle(mModel.getContactName());
+        setDescription(mModel.getUuid());
+        if (mModel.getPhotoData() != null) {
+            mPhoto = mModel.getPhotoData();
         }
-        if (pCallContact.getDisplayName().equals(pCallContact.getRingUsername())) {
-            setType(Type.CONTACT);
+        if (mModel.getContactName().equals(mModel.getUuid())) {
+            if (model.isOnline()) {
+                setType(Type.CONTACT_ONLINE);
+            } else {
+                setType(Type.CONTACT);
+            }
         } else {
-            setType(Type.CONTACT_WITH_USERNAME);
+            if (model.isOnline()) {
+                setType(Type.CONTACT_WITH_USERNAME_ONLINE);
+            } else {
+                setType(Type.CONTACT_WITH_USERNAME);
+            }
         }
     }
 
-    public CallContact getCallContact() {
-        return mCallContact;
+    public SmartListViewModel getModel() {
+        return mModel;
+    }
+
+    public CallContact getContact() {
+        return mContact;
     }
 
     public byte[] getPhoto() {
         return mPhoto;
     }
+
+    @Override
+    public boolean equals(Object pO) {
+        if (this == pO) return true;
+        if (pO == null || getClass() != pO.getClass()) return false;
+
+        ContactCard that = (ContactCard) pO;
+
+        if (mModel != null )
+            return mModel.getUuid().equals(that.mModel.getUuid());
+        if (mContact != null ? !mContact.equals(that.mContact) : that.mContact != null)
+            return false;
+        return Arrays.equals(mPhoto, that.mPhoto);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mModel != null ? mModel.hashCode() : 0;
+        result = 31 * result + (mContact != null ? mContact.hashCode() : 0);
+        result = 31 * result + Arrays.hashCode(mPhoto);
+        return result;
+    }
 }
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 34d3f51..f77fb5f 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
@@ -34,7 +34,7 @@
 
 import cx.ring.R;
 import cx.ring.application.RingApplication;
-import cx.ring.model.CallContact;
+import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.tv.about.AboutActivity;
 import cx.ring.tv.account.TVAccountExport;
 import cx.ring.tv.call.TVCallActivity;
@@ -177,12 +177,30 @@
     }
 
     @Override
-    public void showContacts(final ArrayList<CallContact> contacts) {
+    public void refreshContact(final SmartListViewModel contact) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ContactCard updatedCard = new ContactCard(contact);
+                int pos = cardRowAdapter.indexOf(updatedCard);
+                if (pos > -1) {
+                    ContactCard previousCard = (ContactCard) cardRowAdapter.get(pos);
+                    if (previousCard.getModel().isOnline() != updatedCard.getModel().isOnline()) {
+                        cardRowAdapter.replace(pos, updatedCard);
+                        mRowsAdapter.notifyArrayItemRangeChanged(pos, 1);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void showContacts(final ArrayList<SmartListViewModel> contacts) {
         getActivity().runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 cardRowAdapter.clear();
-                for (CallContact contact : contacts) {
+                for (SmartListViewModel contact : contacts) {
                     cardRowAdapter.add(new ContactCard(contact));
                 }
                 mRowsAdapter.notifyArrayItemRangeChanged(0, contacts.size());
@@ -224,7 +242,7 @@
                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
 
             if (item instanceof ContactCard) {
-                presenter.contactClicked(((ContactCard) item).getCallContact());
+                presenter.contactClicked(((ContactCard) item).getModel());
             } else if (item instanceof IconCard) {
                 IconCard card = (IconCard) item;
                 switch (card.getType()) {
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 3879b7d..7bc49d3 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
@@ -20,6 +20,7 @@
 package cx.ring.tv.main;
 
 import java.util.ArrayList;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 import javax.inject.Inject;
@@ -30,9 +31,12 @@
 import cx.ring.model.CallContact;
 import cx.ring.model.Conversation;
 import cx.ring.model.ServiceEvent;
+import cx.ring.model.Uri;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.services.AccountService;
 import cx.ring.services.ContactService;
+import cx.ring.services.PresenceService;
+import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.utils.Observable;
 import cx.ring.utils.Observer;
 
@@ -47,6 +51,8 @@
 
     private ContactService mContactService;
 
+    private PresenceService mPresenceService;
+
     private ExecutorService mExecutor;
 
     private ArrayList<Conversation> mConversations;
@@ -54,10 +60,12 @@
     @Inject
     public MainPresenter(AccountService accountService,
                          ContactService contactService,
+                         PresenceService presenceService,
                          @Named("ApplicationExecutor") ExecutorService executor,
                          ConversationFacade conversationfacade) {
         mAccountService = accountService;
         mContactService = contactService;
+        mPresenceService = presenceService;
         mConversationFacade = conversationfacade;
         mExecutor = executor;
         mConversations = new ArrayList<>();
@@ -69,6 +77,7 @@
         mAccountService.addObserver(this);
         mConversationFacade.addObserver(this);
         mContactService.addObserver(this);
+        mPresenceService.addObserver(this);
     }
 
     @Override
@@ -77,6 +86,7 @@
         mAccountService.removeObserver(this);
         mConversationFacade.removeObserver(this);
         mContactService.removeObserver(this);
+        mPresenceService.removeObserver(this);
     }
 
     @Override
@@ -93,6 +103,27 @@
                 reloadAccountInfos();
                 break;
         }
+        if (observable instanceof PresenceService) {
+            switch (event.getEventType()) {
+                case NEW_BUDDY_NOTIFICATION:
+                    refreshContact(
+                            event.getString(ServiceEvent.EventInput.BUDDY_URI));
+                           break;
+            }
+        }
+    }
+
+    private void refreshContact(String buddy) {
+        for (Conversation conversation : mConversations) {
+            CallContact callContact = conversation.getContact();
+            if (callContact.getIds().get(0).equals("ring:"+buddy)) {
+                SmartListViewModel smartListViewModel = new SmartListViewModel(conversation,
+                        callContact.getDisplayName(),
+                        callContact.getPhoto());
+                smartListViewModel.setOnline(mPresenceService.isBuddyOnline(callContact.getIds().get(0)));
+                getView().refreshContact(smartListViewModel);
+            }
+        }
     }
 
     public void reloadConversations() {
@@ -102,24 +133,30 @@
             public void run() {
                 mConversations.clear();
                 mConversations.addAll(mConversationFacade.getConversationsList());
-                ArrayList<CallContact> contacts = new ArrayList<>();
+                ArrayList<SmartListViewModel> contacts = new ArrayList<>();
                 if (mConversations != null && mConversations.size() > 0) {
                     for (int i = 0; i < mConversations.size(); i++) {
                         Conversation conversation = mConversations.get(i);
                         CallContact callContact = conversation.getContact();
                         mContactService.loadContactData(callContact);
-                        contacts.add(callContact);
+                        SmartListViewModel smartListViewModel = new SmartListViewModel(conversation,
+                                callContact.getDisplayName(),
+                                callContact.getPhoto());
+                        smartListViewModel.setOnline(mPresenceService.isBuddyOnline(callContact.getIds().get(0)));
+                        contacts.add(smartListViewModel);
                     }
                 }
                 getView().showLoading(false);
                 getView().showContacts(contacts);
             }
         });
+
+        subscribePresence();
     }
 
-    public void contactClicked(CallContact item) {
+    public void contactClicked(SmartListViewModel item) {
         String accountID = mAccountService.getCurrentAccount().getAccountID();
-        String ringID = item.getPhones().get(0).getNumber().toString();
+        String ringID = item.getUuid();
         getView().callContact(accountID, ringID);
     }
 
@@ -137,4 +174,18 @@
     public void onExportClicked() {
         getView().showExportDialog(mAccountService.getCurrentAccount().getAccountID());
     }
+
+    private void subscribePresence() {
+        if (mAccountService.getCurrentAccount() == null) {
+            return;
+        }
+        String accountId = mAccountService.getCurrentAccount().getAccountID();
+        Set<String> keys = mConversationFacade.getConversations().keySet();
+        for (String key : keys) {
+            Uri uri = new Uri(key);
+            if (uri.isRingId()) {
+                mPresenceService.subscribeBuddy(accountId, key, true);
+            }
+        }
+    }
 }
\ No newline at end of file
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 2125c4a..896dba0 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
@@ -21,13 +21,15 @@
 
 import java.util.ArrayList;
 
-import cx.ring.model.CallContact;
+import cx.ring.smartlist.SmartListViewModel;
 
 public interface MainView {
 
     void showLoading(boolean show);
 
-    void showContacts(ArrayList<CallContact> contacts);
+    void refreshContact(SmartListViewModel contact);
+
+    void showContacts(ArrayList<SmartListViewModel> contacts);
 
     void callContact(String accountID, String ringID);
 
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 dcc10f7..22eadf7 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
@@ -41,7 +41,6 @@
 import cx.ring.R;
 import cx.ring.application.RingApplication;
 import cx.ring.model.CallContact;
-import cx.ring.model.Uri;
 import cx.ring.tv.call.TVCallActivity;
 import cx.ring.tv.cards.Card;
 import cx.ring.tv.cards.CardPresenterSelector;
@@ -137,11 +136,11 @@
     }
 
     @Override
-    public void startCall(String accountID, Uri number) {
+    public void startCall(String accountID, String number) {
         Intent intent = new Intent(getActivity(), TVCallActivity.class);
         intent.putExtra("account", accountID);
-        intent.putExtra("ringId", number.toString());
-        startActivity(intent);
+        intent.putExtra("ringId", number);
+        getActivity().startActivity(intent, null);
         getActivity().finish();
     }
 
@@ -149,7 +148,7 @@
         @Override
         public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
-            presenter.contactClicked(((ContactCard) item).getCallContact());
+            presenter.contactClicked(((ContactCard) item).getContact());
         }
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
index 7250b23..d9c1167 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
@@ -145,6 +145,6 @@
     }
 
     public void contactClicked(CallContact contact) {
-        getView().startCall(mAccountService.getCurrentAccount().getAccountID(), contact.getPhones().get(0).getNumber());
+        getView().startCall(mAccountService.getCurrentAccount().getAccountID(), contact.getPhones().get(0).getNumber().toString());
     }
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
index 109d911..c0dbd58 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
@@ -28,5 +28,5 @@
 
     void clearSearch();
 
-    void startCall(String accountID, Uri number);
+    void startCall(String accountID, String number);
 }
diff --git a/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml b/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml
new file mode 100644
index 0000000..6c7ec0c
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <stroke android:width="1px" android:color="#fafafa"/>
+    <solid android:color="#4CAF50" />
+    <size
+        android:width="10dp"
+        android:height="10dp" />
+</shape>
\ 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 c11ea17..0a0f517 100644
--- a/ring-android/app/src/main/res/values/styles.xml
+++ b/ring-android/app/src/main/res/values/styles.xml
@@ -152,10 +152,16 @@
     <style name="ContactTitleViewStyle" parent="DefaultCardStyle">
         <item name="lbImageCardViewType">Title</item>
     </style>
+    <style name="ContactTitleViewOnlineStyle" parent="DefaultCardStyle">
+        <item name="lbImageCardViewType">Title|Content|IconOnRight</item>
+    </style>
 
     <style name="ContactCompleteCardViewStyle" parent="DefaultCardStyle">
         <item name="lbImageCardViewType">Title|Content</item>
     </style>
+    <style name="ContactCompleteCardOnlineViewStyle" parent="DefaultCardStyle">
+        <item name="lbImageCardViewType">Title|Content|IconOnRight</item>
+    </style>
 
     <style name="IconCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
         <item name="android:layout_width">96dp</item>
@@ -208,5 +214,24 @@
         <item name="imageCardViewImageStyle">@style/IconCardImageStyle</item>
         <item name="imageCardViewInfoAreaStyle">@style/IconCardInfoAreaStyle</item>
     </style>
+    <style name="Widget.Leanback.ImageCardView.BadgeStyle">
+        <item name="android:id">@id/extra_badge</item>
+        <item name="android:layout_width">@dimen/lb_basic_card_info_badge_size</item>
+        <item name="android:layout_height">@dimen/lb_basic_card_info_badge_size</item>
+        <item name="android:contentDescription">@null</item>
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+    <style name="OnlineBadgeStyle" parent="Widget.Leanback.ImageCardView.BadgeStyle">
+        <item name="android:src">@drawable/ic_tv_online_indicator</item>
+    </style>
+    <style name="ContactCardOnlineTheme" parent="ContactCardTheme">
+        <item name="imageCardViewBadgeStyle">@style/OnlineBadgeStyle</item>
+        <item name="imageCardViewStyle">@style/ContactCompleteCardViewStyle</item>
+    </style>
+
+    <style name="ContactCompleteCardOnlineTheme" parent="ContactCompleteCardTheme">
+        <item name="imageCardViewStyle">@style/ContactTitleViewOnlineStyle</item>
+        <item name="imageCardViewBadgeStyle">@style/OnlineBadgeStyle</item>
+    </style>
 
 </resources>
\ No newline at end of file