model: cleanup, always retrieve contact data and presence

Change-Id: I17332f126c111695d033b2df1160e1cc970046c5
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 03d3707..5aba4b0 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
@@ -59,6 +59,7 @@
 import butterknife.BindView;
 import butterknife.OnClick;
 import butterknife.OnEditorAction;
+import cx.ring.BuildConfig;
 import cx.ring.R;
 import cx.ring.adapters.ConversationAdapter;
 import cx.ring.adapters.NumberAdapter;
@@ -95,8 +96,8 @@
 
     public static final int REQ_ADD_CONTACT = 42;
 
-    public static final String KEY_CONTACT_RING_ID = "CONTACT_RING_ID";
-    public static final String KEY_ACCOUNT_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";
 
     private static final String CONVERSATION_DELETE = "CONVERSATION_DELETE";
 
@@ -143,7 +144,8 @@
     private AlertDialog mDeleteDialog;
     private boolean mDeleteConversation = false;
 
-    private MenuItem mAddContactBtn = null;
+    private MenuItem mAudioCallBtn = null;
+    private MenuItem mVideoCallBtn = null;
 
     private ConversationAdapter mAdapter = null;
     private NumberAdapter mNumberAdapter = null;
@@ -432,14 +434,10 @@
     }
 
     @Override
-    public void onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        presenter.prepareMenu();
-    }
-
-    @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         inflater.inflate(R.menu.conversation_actions, menu);
+        mAudioCallBtn = menu.findItem(R.id.conv_action_audiocall);
+        mVideoCallBtn = menu.findItem(R.id.conv_action_videocall);
     }
 
     @Override
@@ -532,13 +530,6 @@
     }
 
     @Override
-    public void displayAddContact(final boolean display) {
-        if (mAddContactBtn != null) {
-            mAddContactBtn.setVisible(display);
-        }
-    }
-
-    @Override
     public void displayDeleteDialog(final Conversation conversation) {
         mDeleteDialog = ActionHelper.launchDeleteAction(getActivity(),
                 conversation.getContact(),
@@ -612,12 +603,23 @@
     }
 
     @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        boolean visible = mMessageInput.getVisibility() == View.VISIBLE;
+        if (mAudioCallBtn != null)
+            mAudioCallBtn.setVisible(visible);
+        if (mVideoCallBtn != null)
+            mVideoCallBtn.setVisible(visible);
+    }
+
+    @Override
     public void switchToUnknownView(String contactDisplayName) {
         mMessageInput.setVisibility(View.GONE);
         mUnknownPrompt.setVisibility(View.VISIBLE);
         mTrustRequestPrompt.setVisibility(View.GONE);
         mTvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted), contactDisplayName));
         mTrustRequestMessageLayout.setVisibility(View.VISIBLE);
+        getActivity().invalidateOptionsMenu();
     }
 
     @Override
@@ -627,6 +629,7 @@
         mTrustRequestPrompt.setVisibility(View.VISIBLE);
         mTvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted_yet), contactDisplayName));
         mTrustRequestMessageLayout.setVisibility(View.VISIBLE);
+        getActivity().invalidateOptionsMenu();
     }
 
     @Override
@@ -635,6 +638,7 @@
         mUnknownPrompt.setVisibility(View.GONE);
         mTrustRequestPrompt.setVisibility(View.GONE);
         mTrustRequestMessageLayout.setVisibility(View.GONE);
+        getActivity().invalidateOptionsMenu();
     }
 
     @Override
@@ -653,6 +657,8 @@
     }
 
     private void setLoading(boolean isLoading) {
+        if (takePicture == null || sendData == null || pbDataTransfer == null)
+            return;
         if (isLoading) {
             takePicture.setVisibility(View.GONE);
             sendData.setVisibility(View.GONE);
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 638b5e6..001e98c 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
@@ -113,14 +113,6 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             registerNotificationChannels();
         }
-        mAccountService.getCurrentAccountSubject()
-                .switchMap(a -> a
-                        .getPendingSubject()
-                        .map(p -> {
-                            showIncomingTrustRequestNotification(a);
-                            return a;
-                        }))
-                .subscribe();
     }
 
     @RequiresApi(api = Build.VERSION_CODES.O)
diff --git a/ring-android/app/src/main/res/layout/frag_pending_contact_requests.xml b/ring-android/app/src/main/res/layout/frag_pending_contact_requests.xml
index 4e325ff..2d9b676 100644
--- a/ring-android/app/src/main/res/layout/frag_pending_contact_requests.xml
+++ b/ring-android/app/src/main/res/layout/frag_pending_contact_requests.xml
@@ -25,12 +25,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_below="@+id/pane_ringID"
-        android:layout_marginStart="0dp"
         android:clipToPadding="false"
         android:divider="@null"
         android:paddingTop="8dp"
         android:paddingBottom="8dp"
-        tools:listitem="@layout/item_contact_request" />
+        tools:listitem="@layout/item_smartlist" />
 
     <TextView
         android:id="@+id/emptyTextView"
@@ -38,6 +37,7 @@
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:gravity="center"
-        android:text="@string/no_requests" />
+        android:text="@string/no_requests"
+        android:visibility="gone" />
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
index b1422bb..83270c3 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
@@ -202,7 +202,6 @@
     }
 
     public void prepareMenu() {
-        getView().displayAddContact(mConversation != null && mConversation.getContact().getId() < 0);
     }
 
     public void addContact() {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationView.java b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationView.java
index 964dd84..2b68255 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationView.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationView.java
@@ -43,8 +43,6 @@
 
     void displayNumberSpinner(Conversation conversation, Uri number);
 
-    void displayAddContact(boolean display);
-
     void displayDeleteDialog(Conversation conversation);
 
     void displayCopyToClipboard(CallContact callContact);
@@ -84,5 +82,4 @@
     void addElement(ConversationElement e);
     void updateElement(ConversationElement e);
     void removeElement(ConversationElement e);
-
 }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
index 7f76d46..0c7d34d 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
@@ -88,7 +88,6 @@
     @Inject
     PreferencesService mPreferencesService;
 
-    private Account mConversations = null;
     private final CompositeDisposable mDisposableBag = new CompositeDisposable();
 
     private final Observable<Account> currentAccountSubject;
@@ -108,13 +107,22 @@
                 .subscribe(this::onCallStateChange));
 
         mDisposableBag.add(currentAccountSubject
-                .subscribe(account -> {
-                    Log.d(TAG, "refreshConversations() " + account);
-                    mConversations = account;
-                    for (Conversation conversation : account.getConversations()) {
-                        mPresenceService.subscribeBuddy(account.getAccountID(), conversation.getContact().getPrimaryUri().getRawUriString(), true);
-                    }
-                }));
+                .switchMap(a -> Observable
+                        .merge(a.getConversationsSubject(), a.getPendingSubject())
+                        .doOnNext(c -> {
+                            for (Conversation conversation : c) {
+                                Uri id = conversation.getContact().getPrimaryUri();
+                                mPresenceService.subscribeBuddy(a.getAccountID(), id.getRawUriString(), true);
+                                mContactService.loadContactData(conversation.getContact());
+                                mAccountService.lookupAddress(a.getAccountID(), "", id.getRawRingId());
+                            }
+                        }))
+                .subscribeOn(Schedulers.computation())
+                .subscribe());
+
+        mDisposableBag.add(mAccountService.getIncomingRequests()
+                .switchMapSingle(r -> getAccountSubject(r.getAccountId()))
+                .subscribe(a -> mNotificationService.showIncomingTrustRequestNotification(a)));
 
         mDisposableBag.add(mAccountService
                 .getIncomingMessages()
@@ -133,6 +141,7 @@
                         conv.updateTextMessage(txt);
                     }
                 }));
+
         mDisposableBag.add(mAccountService.getDataTransfers().subscribe(this::handleDataTransferEvent));
     }
 
@@ -294,10 +303,10 @@
                 });
     }
 
-    public void updateTextNotifications() {
+    public void updateTextNotifications(List<Conversation> conversations) {
         Log.d(TAG, "updateTextNotifications()");
 
-        for (Conversation conversation : mConversations.getConversations()) {
+        for (Conversation conversation : conversations) {
             TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages();
 
             CallContact contact = conversation.getContact();
@@ -316,7 +325,11 @@
         if (txt.isRead()) {
             mHistoryService.updateTextMessage(new HistoryText(txt)).subscribe();
         }
-        updateTextNotifications();
+        getAccountSubject(txt.getAccount())
+                .flatMapObservable(Account::getConversationsSubject)
+                .firstOrError()
+                .subscribeOn(Schedulers.computation())
+                .subscribe(this::updateTextNotifications);
     }
 
     public void acceptRequest(String accountId, Uri contactUri) {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java b/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
index d2703a5..d5c28f3 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
@@ -149,7 +149,7 @@
     }
 
     public ArrayList<String> getIds() {
-        ArrayList<String> ret = new ArrayList<>(1 + mPhones.size());
+        ArrayList<String> ret = new ArrayList<>(mPhones.size() + (mId == UNKNOWN_ID ? 0 : 1));
         if (mId != UNKNOWN_ID)
             ret.add("c:" + Long.toHexString(mId));
         for (Phone p : mPhones)
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java b/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
index b42fa60..925246f 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
@@ -113,6 +113,7 @@
     private final Subject<TextMessage> incomingMessageSubject = PublishSubject.create();
     private final Subject<TextMessage> messageSubject = PublishSubject.create();
     private final Subject<DataTransfer> dataTransferSubject = PublishSubject.create();
+    private final Subject<TrustRequest> incomingRequestsSubject = PublishSubject.create();
 
     public class RegisteredName {
         public String accountId;
@@ -149,6 +150,9 @@
     public Observable<TextMessage> getMessageStateChanges() {
         return messageSubject;
     }
+    public Observable<TrustRequest> getIncomingRequests() {
+        return incomingRequestsSubject;
+    }
 
     /**
      * @return true if at least one of the loaded accounts is a SIP one
@@ -1047,7 +1051,9 @@
             }
             account.addRequest(request);
             lookupAddress(accountId, "", from);
+            incomingRequestsSubject.onNext(request);
         }
+
     }
 
     void contactAdded(String accountId, String uri, boolean confirmed) {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/PresenceService.java b/ring-android/libringclient/src/main/java/cx/ring/services/PresenceService.java
index d6a92bb..bc147e9 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/PresenceService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/PresenceService.java
@@ -49,26 +49,12 @@
     @Inject
     ContactService mContactService;
 
-    private final Map<String, Boolean> mPresenceMap = new HashMap<>();
     private final PublishSubject<CallContact> presentSubject = PublishSubject.create();
 
     public Observable<CallContact> getPresenceUpdates() {
         return presentSubject;
     }
 
-    /**
-     * Check service cache for latest presence value
-     *
-     * @param uri URI of the contact
-     * @return true if this URI is online according to latest daemon update
-     */
-    public boolean isBuddyOnline(String uri) {
-        if (uri != null && mPresenceMap.containsKey(uri)) {
-            return mPresenceMap.get(uri);
-        }
-        return false;
-    }
-
     public void publish(final String accountID, final boolean status, final String note) {
         mExecutor.execute(() -> Ringservice.publish(accountID, status, note));
     }
@@ -103,9 +89,6 @@
     }
 
     public void newBuddyNotification(String accountId, String buddyUri, int status, String lineStatus) {
-        Log.d(TAG, "newBuddyNotification: " + accountId + ", " + buddyUri + ", " + status + ", " + lineStatus);
-        mPresenceMap.put(CallContact.PREFIX_RING + buddyUri, status == 1);
-
         CallContact contact = mContactService.findContact(accountId, new Uri(CallContact.PREFIX_RING + buddyUri));
         contact.setOnline(status == 1);
         presentSubject.onNext(contact);
diff --git a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
index e5613f8..1f19c17 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
@@ -30,7 +30,6 @@
 import cx.ring.facades.ConversationFacade;
 import cx.ring.model.Account;
 import cx.ring.model.CallContact;
-import cx.ring.model.Conversation;
 import cx.ring.model.Phone;
 import cx.ring.model.RingError;
 import cx.ring.model.Uri;
@@ -71,10 +70,8 @@
 
     private final PublishSubject<String> contactQuery = PublishSubject.create();
     private final Observable<Account> accountSubject;
-    private final Observable<ArrayList<SmartListViewModel>> conversationViews;
+    private final Observable<List<SmartListViewModel>> conversationViews;
 
-    //@Inject
-    //@Named("UiScheduler")
     private final Scheduler mUiScheduler;
 
     private CompositeDisposable mConversationDisposable;
@@ -101,14 +98,7 @@
                 .share();
 
         conversationViews = accountSubject
-                .switchMap(a -> a
-                        .getConversationsSubject()
-                        .map(conversations -> {
-                            ArrayList<SmartListViewModel> viewModel = new ArrayList<>(conversations.size());
-                            for (Conversation c : conversations)
-                                viewModel.add(modelToViewModel(a, c));
-                            return viewModel;
-                        }))
+                .switchMap(Account::getConversationsViewModels)
                 .observeOn(mUiScheduler);
     }
 
@@ -277,9 +267,7 @@
 
         Log.w(TAG, "loadConversations() subscribe");
         mConversationDisposable.add(accountSubject
-                        .switchMap(a -> a
-                                .getConversationSubject()
-                                .map(c -> modelToViewModel(a, c)))
+                        .switchMap(Account::getConversationViewModel)
                         .observeOn(mUiScheduler)
                         .subscribe(vm -> {
                             Log.d(TAG, "getConversationSubject " + vm);
@@ -304,16 +292,6 @@
                 }));
     }
 
-    private SmartListViewModel modelToViewModel(Account account, Conversation conversation) {
-        CallContact contact = conversation.getContact();
-        String primaryId = contact.getIds().get(0);
-        mContactService.loadContactData(contact);
-        SmartListViewModel smartListViewModel = new SmartListViewModel(account.getAccountID(), contact, primaryId,
-                conversation.getLastEvent());
-        smartListViewModel.setOnline(mPresenceService.isBuddyOnline(primaryId));
-        return smartListViewModel;
-    }
-
     private List<SmartListViewModel> filter(List<SmartListViewModel> list, String query) {
         if (StringUtils.isEmpty(query))
             return list;