model: remove android refs from Conversation

Tuleap: #1313
Change-Id: I0f56739ad59049b9f9a787ae6e6f75f0016db776
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 2247cea..d977ec5 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
@@ -22,6 +22,7 @@
 package cx.ring.adapters;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Typeface;
 import android.text.TextUtils;
@@ -40,14 +41,19 @@
 import java.text.Normalizer;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.concurrent.ExecutorService;
 
 import butterknife.BindView;
 import butterknife.ButterKnife;
 import cx.ring.R;
+import cx.ring.history.HistoryCall;
+import cx.ring.history.HistoryEntry;
+import cx.ring.history.Tuple;
 import cx.ring.model.CallContact;
 import cx.ring.model.Conversation;
+import cx.ring.model.TextMessage;
 
 public class SmartListAdapter extends BaseAdapter {
 
@@ -173,7 +179,7 @@
         long lastInteraction = holder.conv.getLastInteraction().getTime();
         holder.convTime.setText(lastInteraction == 0 ? "" :
                 DateUtils.getRelativeTimeSpanString(lastInteraction, System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL));
-        holder.convStatus.setText(holder.conv.getLastInteractionSumary(mContext.getResources()));
+        holder.convStatus.setText(getLastInteractionSummary(holder.conv, mContext.getResources()));
         if (holder.conv.hasUnreadTextMessages()) {
             holder.convParticipants.setTypeface(null, Typeface.BOLD);
             holder.convTime.setTypeface(null, Typeface.BOLD);
@@ -249,4 +255,47 @@
         }
         this.mCallbacks = callback;
     }
+
+    private String getLastInteractionSummary(Conversation conversation, Resources resources) {
+        if (conversation.hasCurrentCall()) {
+            return resources.getString(R.string.ongoing_call);
+        }
+        Tuple<Date, String> d = new Tuple<>(new Date(0), null);
+
+        for (HistoryEntry e : conversation.getHistory().values()) {
+            Date entryDate = e.getLastInteractionDate();
+            String entrySummary = getLastInteractionSummary(e, resources);
+            if (entryDate == null || entrySummary == null) {
+                continue;
+            }
+            Tuple<Date, String> tmp = new Tuple<>(entryDate, entrySummary);
+            if (d.first.compareTo(entryDate) < 0) {
+                d = tmp;
+            }
+        }
+        return d.second;
+    }
+
+    private String getLastInteractionSummary(HistoryEntry e, Resources resources) {
+        long lastTextTimestamp = e.getTextMessages().isEmpty() ? 0 : e.getTextMessages().lastEntry().getKey();
+        long lastCallTimestamp = e.getCalls().isEmpty() ? 0 : e.getCalls().lastEntry().getKey();
+        if (lastTextTimestamp > 0 && lastTextTimestamp > lastCallTimestamp) {
+            TextMessage msg = e.getTextMessages().lastEntry().getValue();
+            String msgString = msg.getMessage();
+            if (msgString != null && !msgString.isEmpty() && msgString.contains("\n")) {
+                int lastIndexOfChar = msgString.lastIndexOf("\n");
+                if (lastIndexOfChar + 1 < msgString.length()) {
+                    msgString = msgString.substring(msgString.lastIndexOf("\n") + 1);
+                }
+            }
+            return (msg.isIncoming() ? "" : resources.getText(R.string.you_txt_prefix) + " ") + msgString;
+        }
+        if (lastCallTimestamp > 0) {
+            HistoryCall lastCall = e.getCalls().lastEntry().getValue();
+            return String.format(resources.getString(lastCall.isIncoming()
+                    ? R.string.hist_in_call
+                    : R.string.hist_out_call), lastCall.getDurationString());
+        }
+        return null;
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
index 20fed56..8f04fba 100644
--- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
@@ -283,7 +283,7 @@
 
         // If no number found, use first from contact
         if (number == null || number.isEmpty())
-            number = conv.contact.getPhones().get(0).getNumber();
+            number = conv.getContact().getPhones().get(0).getNumber();
 
         return new Pair<>(a, number);
     }
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 1b39a52..14231a1 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
@@ -66,6 +66,7 @@
 import cx.ring.model.Conversation;
 import cx.ring.model.SipUri;
 import cx.ring.service.LocalService;
+import cx.ring.utils.ActionHelper;
 import cx.ring.utils.ClipboardHelper;
 import cx.ring.utils.ContentUriHandler;
 
@@ -185,7 +186,7 @@
             });
         }
 
-        mAdapter.updateDataset(mConversation.getHistory(), refreshed);
+        mAdapter.updateDataset(mConversation.getAggregateHistory(), refreshed);
 
         if (mConversation.getContact().getPhones().size() > 1) {
             mNumberSpinner.setVisibility(View.VISIBLE);
@@ -227,13 +228,13 @@
             registerReceiver(receiver, filter);
 
             mBound = true;
-            if (mVisible && mConversation != null && !mConversation.mVisible) {
-                mConversation.mVisible = true;
+            if (mVisible && mConversation != null && !mConversation.isVisible()) {
+                mConversation.setVisible(true);
                 mService.readConversation(mConversation);
             }
 
             if (mDeleteConversation) {
-                mDeleteDialog = Conversation.launchDeleteAction(ConversationActivity.this, mConversation, ConversationActivity.this);
+                mDeleteDialog = ActionHelper.launchDeleteAction(ConversationActivity.this, mConversation, ConversationActivity.this);
             }
 
             mRefreshTaskHandler.postDelayed(refreshTask, REFRESH_INTERVAL_MS);
@@ -245,7 +246,7 @@
             mBound = false;
             mRefreshTaskHandler.removeCallbacks(refreshTask);
             if (mConversation != null) {
-                mConversation.mVisible = false;
+                mConversation.setVisible(false);
             }
         }
     };
@@ -349,7 +350,7 @@
         mVisible = false;
         if (mConversation != null) {
             mService.readConversation(mConversation);
-            mConversation.mVisible = false;
+            mConversation.setVisible(false);
         }
     }
 
@@ -359,7 +360,7 @@
         Log.d(TAG, "onResume " + mConversation);
         mVisible = true;
         if (mConversation != null) {
-            mConversation.mVisible = true;
+            mConversation.setVisible(true);
             if (mBound && mService != null) {
                 mService.readConversation(mConversation);
             }
@@ -429,13 +430,13 @@
                 onCallWithVideo(true);
                 return true;
             case R.id.menuitem_addcontact:
-                startActivityForResult(mConversation.contact.getAddNumberIntent(), REQ_ADD_CONTACT);
+                startActivityForResult(mConversation.getContact().getAddNumberIntent(), REQ_ADD_CONTACT);
                 return true;
             case R.id.menuitem_delete:
-                mDeleteDialog = Conversation.launchDeleteAction(this, this.mConversation, this);
+                mDeleteDialog = ActionHelper.launchDeleteAction(this, this.mConversation, this);
                 return true;
             case R.id.menuitem_copy_content:
-                Conversation.launchCopyNumberToClipboardFromContact(this,
+                ActionHelper.launchCopyNumberToClipboardFromContact(this,
                         this.mConversation.getContact(), this);
                 return true;
             default:
@@ -471,7 +472,7 @@
 
         // If no number found, use first from contact
         if (number == null || number.isEmpty())
-            number = mConversation.contact.getPhones().get(0).getNumber();
+            number = mConversation.getContact().getPhones().get(0).getNumber();
 
         return new Pair<>(a, number);
     }
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 2ba7f50..cb264f3 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
@@ -25,6 +25,7 @@
 import android.app.Fragment;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
@@ -36,6 +37,7 @@
 import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.Snackbar;
 import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.SearchView;
 import android.support.v7.widget.Toolbar;
 import android.text.InputType;
@@ -75,6 +77,7 @@
 import cx.ring.model.Conversation;
 import cx.ring.navigation.RingNavigationFragment;
 import cx.ring.service.LocalService;
+import cx.ring.utils.ActionHelper;
 import cx.ring.utils.BlockchainInputHandler;
 import cx.ring.utils.ClipboardHelper;
 import cx.ring.utils.ContentUriHandler;
@@ -428,13 +431,46 @@
             new AdapterView.OnItemLongClickListener() {
                 @Override
                 public boolean onItemLongClick(AdapterView<?> parent, View v, int position, long id) {
-                    Conversation.presentActions(getActivity(),
+                    presentActions(getActivity(),
                             ((SmartListAdapter.ViewHolder) v.getTag()).conv,
                             SmartListFragment.this);
                     return true;
                 }
             };
 
+    public static void presentActions(final Activity activity,
+                                      final Conversation conversation,
+                                      final Conversation.ConversationActionCallback callback) {
+        if (activity == null) {
+            cx.ring.utils.Log.d(TAG, "presentActions: activity is null");
+            return;
+        }
+
+        if (conversation == null) {
+            cx.ring.utils.Log.d(TAG, "presentActions: conversation is null");
+            return;
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setItems(R.array.conversation_actions, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                switch (which) {
+                    case 0:
+                        ActionHelper.launchCopyNumberToClipboardFromContact(activity,
+                                conversation.getContact(),
+                                callback);
+                        break;
+                    case 1:
+                        ActionHelper.launchDeleteAction(activity, conversation, callback);
+                        break;
+                }
+            }
+        });
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
diff --git a/ring-android/app/src/main/java/cx/ring/model/Conversation.java b/ring-android/app/src/main/java/cx/ring/model/Conversation.java
index 9bd9975..e781f84 100644
--- a/ring-android/app/src/main/java/cx/ring/model/Conversation.java
+++ b/ring-android/app/src/main/java/cx/ring/model/Conversation.java
@@ -20,14 +20,6 @@
 
 package cx.ring.model;
 
-import android.app.Activity;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.database.ContentObservable;
-import android.support.v4.app.NotificationCompat;
-import android.support.v7.app.AlertDialog;
-import android.util.Log;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,72 +31,44 @@
 import java.util.Set;
 import java.util.TreeMap;
 
-import cx.ring.R;
-import cx.ring.adapters.NumberAdapter;
 import cx.ring.history.HistoryCall;
 import cx.ring.history.HistoryEntry;
 import cx.ring.history.Tuple;
+import cx.ring.utils.Log;
 
-public class Conversation extends ContentObservable {
-    static final String TAG = Conversation.class.getSimpleName();
-    private final static Random rand = new Random();
+public class Conversation {
 
-    public CallContact contact;
+    private static final String TAG = Conversation.class.getSimpleName();
 
-    private final Map<String, HistoryEntry> history = new HashMap<>();
-    public final ArrayList<Conference> current_calls;
-    private final ArrayList<ConversationElement> agregate_history = new ArrayList<>(32);
+    private CallContact mContact;
 
-    // runtime flag set to true if the user
-    public boolean mVisible = false;
-    public int notificationId;
-    public NotificationCompat.Builder notificationBuilder = null;
+    private final Map<String, HistoryEntry> mHistory = new HashMap<>();
+    private final ArrayList<Conference> mCurrentCalls;
+    private final ArrayList<ConversationElement> mAggregateHistory = new ArrayList<>(32);
 
-    public String getLastNumberUsed(String accountID) {
-        HistoryEntry he = history.get(accountID);
-        if (he == null)
-            return null;
-        return he.getLastNumberUsed();
-    }
+    // runtime flag set to true if the user is currently viewing this conversation
+    private boolean mVisible = false;
+    private int uuid;
 
-    public Conference getConference(String id) {
-        for (Conference c : current_calls)
-            if (c.getId().contentEquals(id) || c.getCallById(id) != null)
-                return c;
-        return null;
-    }
-
-    public void addConference(Conference c) {
-        current_calls.add(c);
-    }
-
-    public void removeConference(Conference c) {
-        current_calls.remove(c);
-    }
-
-    public Tuple<HistoryEntry, HistoryCall> findHistoryByCallId(String id) {
-        for (HistoryEntry e : history.values()) {
-            for (HistoryCall c : e.getCalls().values()) {
-                if (c.getCallId().equals(id))
-                    return new Tuple<>(e, c);
-            }
-        }
-        return null;
+    public Conversation(CallContact c) {
+        setContact(c);
+        mCurrentCalls = new ArrayList<>();
+        setUuid(new Random().nextInt());
     }
 
     public class ConversationElement {
         public HistoryCall call = null;
         public TextMessage text = null;
 
-        public ConversationElement(HistoryCall c) {
+        ConversationElement(HistoryCall c) {
             call = c;
         }
 
-        public ConversationElement(TextMessage t) {
+        ConversationElement(TextMessage t) {
             text = t;
         }
 
-        public long getDate() {
+        long getDate() {
             if (text != null)
                 return text.getTimestamp();
             else if (call != null)
@@ -113,124 +77,137 @@
         }
     }
 
-    public Conversation(CallContact c) {
-        contact = c;
-        current_calls = new ArrayList<>();
-        notificationId = rand.nextInt();
+    public boolean hasCurrentCall() {
+        return !mCurrentCalls.isEmpty();
     }
 
-    public CallContact getContact() {
-        return contact;
+    public String getLastNumberUsed(String accountID) {
+        HistoryEntry he = mHistory.get(accountID);
+        if (he == null)
+            return null;
+        return he.getLastNumberUsed();
     }
 
-    public Date getLastInteraction() {
-        if (!current_calls.isEmpty()) {
-            return new Date();
-        }
-        Date d = new Date(0);
-
-        //for (Map.Entry<String, HistoryEntry> e : history.entrySet()) {
-        for (HistoryEntry e : history.values()) {
-            Date nd = e.getLastInteractionDate();
-            if (d.compareTo(nd) < 0)
-                d = nd;
-        }
-        return d;
+    public Conference getConference(String id) {
+        for (Conference c : mCurrentCalls)
+            if (c.getId().contentEquals(id) || c.getCallById(id) != null)
+                return c;
+        return null;
     }
 
-    public String getLastInteractionSumary(Resources resources) {
-        if (!current_calls.isEmpty()) {
-            return resources.getString(R.string.ongoing_call);
-        }
-        Tuple<Date, String> d = new Tuple<>(new Date(0), null);
+    public void addConference(Conference c) {
+        mCurrentCalls.add(c);
+    }
 
-        for (HistoryEntry e : history.values()) {
-            Date entryDate = e.getLastInteractionDate();
-            String entrySummary = getLastInteractionSummary(e, resources);
-            if (entryDate == null || entrySummary == null) {
-                continue;
+    public void removeConference(Conference c) {
+        mCurrentCalls.remove(c);
+    }
+
+    public Tuple<HistoryEntry, HistoryCall> findHistoryByCallId(String id) {
+        for (HistoryEntry e : mHistory.values()) {
+            for (HistoryCall c : e.getCalls().values()) {
+                if (c.getCallId().equals(id))
+                    return new Tuple<>(e, c);
             }
-            Tuple<Date, String> tmp = new Tuple<>(entryDate, entrySummary);
-            if (d.first.compareTo(entryDate) < 0) {
-                d = tmp;
-            }
-        }
-        return d.second;
-    }
-
-    private String getLastInteractionSummary(HistoryEntry e, Resources resources) {
-        long lastTextTimestamp = e.getTextMessages().isEmpty() ? 0 : e.getTextMessages().lastEntry().getKey();
-        long lastCallTimestamp = e.getCalls().isEmpty() ? 0 : e.getCalls().lastEntry().getKey();
-        if (lastTextTimestamp > 0 && lastTextTimestamp > lastCallTimestamp) {
-            TextMessage msg = e.getTextMessages().lastEntry().getValue();
-            String msgString = msg.getMessage();
-            if (msgString != null && !msgString.isEmpty() && msgString.contains("\n")) {
-                int lastIndexOfChar = msgString.lastIndexOf("\n");
-                if (lastIndexOfChar + 1 < msgString.length()) {
-                    msgString = msgString.substring(msgString.lastIndexOf("\n") + 1);
-                }
-            }
-            return (msg.isIncoming() ? "" : resources.getText(R.string.you_txt_prefix) + " ") + msgString;
-        }
-        if (lastCallTimestamp > 0) {
-            HistoryCall lastCall = e.getCalls().lastEntry().getValue();
-            return String.format(resources.getString(lastCall.isIncoming()
-                    ? R.string.hist_in_call
-                    : R.string.hist_out_call), lastCall.getDurationString());
         }
         return null;
     }
 
+    public void setContact(CallContact contact) {
+        this.mContact = contact;
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    public void setVisible(boolean mVisible) {
+        this.mVisible = mVisible;
+    }
+
+    public int getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(int uuid) {
+        this.uuid = uuid;
+    }
+
+    public CallContact getContact() {
+        return mContact;
+    }
+
+    public Date getLastInteraction() {
+        if (!mCurrentCalls.isEmpty()) {
+            return new Date();
+        }
+        Date d = new Date(0);
+
+        for (HistoryEntry e : mHistory.values()) {
+            Date nd = e.getLastInteractionDate();
+            if (d.compareTo(nd) < 0) {
+                d = nd;
+            }
+        }
+        return d;
+    }
+
     public void addHistoryCall(HistoryCall c) {
         String accountId = c.getAccountID();
-        if (history.containsKey(accountId))
-            history.get(accountId).addHistoryCall(c, contact);
+        if (mHistory.containsKey(accountId))
+            mHistory.get(accountId).addHistoryCall(c, getContact());
         else {
-            HistoryEntry e = new HistoryEntry(accountId, contact);
-            e.addHistoryCall(c, contact);
-            history.put(accountId, e);
+            HistoryEntry e = new HistoryEntry(accountId, getContact());
+            e.addHistoryCall(c, getContact());
+            mHistory.put(accountId, e);
         }
-        agregate_history.add(new ConversationElement(c));
+        mAggregateHistory.add(new ConversationElement(c));
     }
 
     public void addTextMessage(TextMessage txt) {
         if (txt.getCallId() != null && !txt.getCallId().isEmpty()) {
             Conference conf = getConference(txt.getCallId());
-            if (conf == null)
+            if (conf == null) {
                 return;
+            }
             conf.addSipMessage(txt);
         }
-        if (txt.getContact() == null)
-            txt.setContact(contact);
-        String accountId = txt.getAccount();
-        if (history.containsKey(accountId))
-            history.get(accountId).addTextMessage(txt);
-        else {
-            HistoryEntry e = new HistoryEntry(accountId, contact);
-            e.addTextMessage(txt);
-            history.put(accountId, e);
+        if (txt.getContact() == null) {
+            txt.setContact(getContact());
         }
-        agregate_history.add(new ConversationElement(txt));
+        String accountId = txt.getAccount();
+        if (mHistory.containsKey(accountId)) {
+            mHistory.get(accountId).addTextMessage(txt);
+        } else {
+            HistoryEntry e = new HistoryEntry(accountId, getContact());
+            e.addTextMessage(txt);
+            mHistory.put(accountId, e);
+        }
+        mAggregateHistory.add(new ConversationElement(txt));
     }
 
-    public ArrayList<ConversationElement> getHistory() {
-        Collections.sort(agregate_history, new Comparator<ConversationElement>() {
+    public Map<String, HistoryEntry> getHistory() {
+        return mHistory;
+    }
+
+    public ArrayList<ConversationElement> getAggregateHistory() {
+        Collections.sort(mAggregateHistory, new Comparator<ConversationElement>() {
             @Override
             public int compare(ConversationElement lhs, ConversationElement rhs) {
                 return (int) ((lhs.getDate() - rhs.getDate()) / 1000L);
             }
         });
-        return agregate_history;
+        return mAggregateHistory;
     }
 
     public Set<String> getAccountsUsed() {
-        return history.keySet();
+        return mHistory.keySet();
     }
 
     public String getLastAccountUsed() {
         String last = null;
         Date d = new Date(0);
-        for (Map.Entry<String, HistoryEntry> e : history.entrySet()) {
+        for (Map.Entry<String, HistoryEntry> e : mHistory.entrySet()) {
             Date nd = e.getValue().getLastInteractionDate();
             if (d.compareTo(nd) < 0) {
                 d = nd;
@@ -242,13 +219,13 @@
     }
 
     public Conference getCurrentCall() {
-        if (current_calls.isEmpty())
+        if (mCurrentCalls.isEmpty())
             return null;
-        return current_calls.get(0);
+        return mCurrentCalls.get(0);
     }
 
     public ArrayList<Conference> getCurrentCalls() {
-        return current_calls;
+        return mCurrentCalls;
     }
 
     public Collection<TextMessage> getTextMessages() {
@@ -257,7 +234,7 @@
 
     public Collection<TextMessage> getTextMessages(Date since) {
         TreeMap<Long, TextMessage> texts = new TreeMap<>();
-        for (HistoryEntry h : history.values()) {
+        for (HistoryEntry h : mHistory.values()) {
             texts.putAll(since == null ? h.getTextMessages() : h.getTextMessages(since.getTime()));
         }
         return texts.values();
@@ -265,7 +242,7 @@
 
     public TreeMap<Long, TextMessage> getUnreadTextMessages() {
         TreeMap<Long, TextMessage> texts = new TreeMap<>();
-        for (HistoryEntry h : history.values()) {
+        for (HistoryEntry h : mHistory.values()) {
             for (Map.Entry<Long, TextMessage> entry : h.getTextMessages().descendingMap().entrySet())
                 if (entry.getValue().isRead())
                     break;
@@ -276,16 +253,17 @@
     }
 
     public boolean hasUnreadTextMessages() {
-        for (HistoryEntry h : history.values()) {
+        for (HistoryEntry h : mHistory.values()) {
             Map.Entry<Long, TextMessage> m = h.getTextMessages().lastEntry();
-            if (m != null && !m.getValue().isRead())
+            if (m != null && !m.getValue().isRead()) {
                 return true;
+            }
         }
         return false;
     }
 
     public Map<String, HistoryEntry> getRawHistory() {
-        return history;
+        return mHistory;
     }
 
     public interface ConversationActionCallback {
@@ -293,115 +271,4 @@
 
         void copyContactNumberToClipboard(String contactNumber);
     }
-
-    public static AlertDialog launchDeleteAction(final Activity activity,
-                                                 final Conversation conversation,
-                                                 final ConversationActionCallback callback) {
-        if (activity == null) {
-            Log.d(TAG, "launchDeleteAction: activity is null");
-            return null;
-        }
-
-        if (conversation == null) {
-            Log.d(TAG, "launchDeleteAction: conversation is null");
-            return null;
-        }
-
-        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
-        builder.setTitle(R.string.conversation_action_delete_this_title)
-                .setMessage(R.string.conversation_action_delete_this_message)
-                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int whichButton) {
-                        if (callback != null) {
-                            callback.deleteConversation(conversation);
-                        }
-                    }
-                })
-                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int whichButton) {
-                        /* Terminate with no action */
-                    }
-                });
-
-        AlertDialog alertDialog = builder.create();
-        alertDialog.show();
-        return alertDialog;
-    }
-
-    public static void presentActions(final Activity activity,
-                                      final Conversation conversation,
-                                      final ConversationActionCallback callback) {
-        if (activity == null) {
-            Log.d(TAG, "presentActions: activity is null");
-            return;
-        }
-
-        if (conversation == null) {
-            Log.d(TAG, "presentActions: conversation is null");
-            return;
-        }
-
-        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
-        builder.setItems(R.array.conversation_actions, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which) {
-                switch (which) {
-                    case 0:
-                        launchCopyNumberToClipboardFromContact(activity,
-                                conversation.contact,
-                                callback);
-                        break;
-                    case 1:
-                        launchDeleteAction(activity, conversation, callback);
-                        break;
-                }
-            }
-        });
-        AlertDialog dialog = builder.create();
-        dialog.show();
-    }
-
-    public static void launchCopyNumberToClipboardFromContact(final Activity activity,
-                                                              final CallContact callContact,
-                                                              final ConversationActionCallback callback) {
-        if (callContact == null) {
-            Log.d(TAG, "launchCopyNumberToClipboardFromContact: callContact is null");
-            return;
-        }
-
-        if (activity == null) {
-            Log.d(TAG, "launchCopyNumberToClipboardFromContact: activity is null");
-            return;
-        }
-
-        if (callContact.getPhones().isEmpty()) {
-            Log.d(TAG, "launchCopyNumberToClipboardFromContact: no number to copy");
-            return;
-        } else if (callContact.getPhones().size() == 1 && callback != null) {
-            String number = callContact.getPhones().get(0).getNumber().toString();
-            callback.copyContactNumberToClipboard(number);
-            return;
-        }
-
-        final NumberAdapter adapter = new NumberAdapter(activity, callContact, true);
-        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
-        builder.setTitle(R.string.conversation_action_select_peer_number);
-        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which) {
-                if (callback != null) {
-                    CallContact.Phone selectedPhone = (CallContact.Phone) adapter.getItem(which);
-                    callback.copyContactNumberToClipboard(selectedPhone.getNumber().toString());
-                }
-            }
-        });
-        AlertDialog dialog = builder.create();
-        final int listViewSidePadding = (int) activity
-                .getResources()
-                .getDimension(R.dimen.alert_dialog_side_padding_list_view);
-        dialog.getListView().setPadding(listViewSidePadding, 0, listViewSidePadding, 0);
-        dialog.show();
-    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
index cc3d542..629bfd6 100644
--- a/ring-android/app/src/main/java/cx/ring/service/LocalService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
@@ -163,6 +163,7 @@
     private boolean canUseMobile = false;
 
     private boolean mAreConversationsLoaded = false;
+    private NotificationCompat.Builder mMessageNotificationBuilder;
 
     public interface NameLookupCallback {
         void onFound(String name, String address);
@@ -363,7 +364,7 @@
                 readTextMessage(msg);
             }
         }
-        notificationManager.cancel(conv.notificationId);
+        notificationManager.cancel(conv.getUuid());
         updateTextNotifications();
     }
 
@@ -383,7 +384,7 @@
         Log.d(TAG, "Sent text messsage " + txt.getAccount() + " " + txt.getCallId() + " " + txt.getNumberUri() + " " + txt.getMessage());
         Conversation conv = conversationFromMessage(txt);
         conv.addTextMessage(txt);
-        if (conv.mVisible) {
+        if (conv.isVisible()) {
             txt.read();
         } else {
             updateTextNotifications();
@@ -784,8 +785,8 @@
 
     public CallContact findContactByNumber(SipUri number) {
         for (Conversation conversation : conversations.values()) {
-            if (conversation.contact.hasNumber(number)) {
-                return conversation.contact;
+            if (conversation.getContact().hasNumber(number)) {
+                return conversation.getContact();
             }
         }
         return canUseContacts ? findContactByNumber(getContentResolver(), number.getRawUriString()) : CallContact.buildUnknown(number);
@@ -796,7 +797,7 @@
             return null;
         }
         for (Conversation conversation : conversations.values()) {
-            if (conversation.contact.hasNumber(number)) {
+            if (conversation.getContact().hasNumber(number)) {
                 return conversation;
             }
         }
@@ -1108,7 +1109,7 @@
                     Map.Entry<String, Conversation> merge = null;
                     for (Map.Entry<String, Conversation> ce : ret.entrySet()) {
                         Conversation conversation = ce.getValue();
-                        if ((contact.getId() > 0 && contact.getId() == conversation.contact.getId()) || conversation.contact.hasNumber(call.getNumber())) {
+                        if ((contact.getId() > 0 && contact.getId() == conversation.getContact().getId()) || conversation.getContact().hasNumber(call.getNumber())) {
                             merge = ce;
                             break;
                         }
@@ -1116,7 +1117,7 @@
                     if (merge != null) {
                         Conversation conversation = merge.getValue();
                         if (conversation.getContact().getId() <= 0 && contact.getId() > 0) {
-                            conversation.contact = contact;
+                            conversation.setContact(contact);
                             ret.remove(merge.getKey());
                             ret.put(contact.getIds().get(0), conversation);
                         }
@@ -1221,7 +1222,7 @@
 
     private void updated(Map<String, Conversation> res) {
         for (Conversation conversation : conversations.values()) {
-            for (Conference conference : conversation.current_calls) {
+            for (Conference conference : conversation.getCurrentCalls()) {
                 notificationManager.cancel(conference.notificationId);
             }
         }
@@ -1265,51 +1266,50 @@
             if (texts.isEmpty() || texts.lastEntry().getValue().isNotified()) {
                 continue;
             } else {
-                notificationManager.cancel(c.notificationId);
+                notificationManager.cancel(c.getUuid());
             }
 
             CallContact contact = c.getContact();
-            if (c.notificationBuilder == null) {
-                c.notificationBuilder = new NotificationCompat.Builder(getApplicationContext());
-                c.notificationBuilder.setCategory(NotificationCompat.CATEGORY_MESSAGE)
+            if (mMessageNotificationBuilder == null) {
+                mMessageNotificationBuilder = new NotificationCompat.Builder(getApplicationContext());
+                mMessageNotificationBuilder.setCategory(NotificationCompat.CATEGORY_MESSAGE)
                         .setPriority(NotificationCompat.PRIORITY_HIGH)
                         .setDefaults(NotificationCompat.DEFAULT_ALL)
                         .setSmallIcon(R.drawable.ic_ring_logo_white)
                         .setContentTitle(contact.getDisplayName());
             }
-            NotificationCompat.Builder noti = c.notificationBuilder;
             Intent c_intent = new Intent(Intent.ACTION_VIEW)
                     .setClass(this, ConversationActivity.class)
                     .setData(Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, contact.getIds().get(0)));
             Intent d_intent = new Intent(ACTION_CONV_READ)
                     .setClass(this, LocalService.class)
                     .setData(Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, contact.getIds().get(0)));
-            noti.setContentIntent(PendingIntent.getActivity(this, new Random().nextInt(), c_intent, 0))
+            mMessageNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, new Random().nextInt(), c_intent, 0))
                     .setDeleteIntent(PendingIntent.getService(this, new Random().nextInt(), d_intent, 0));
 
             if (contact.getPhoto() != null) {
                 Resources res = getResources();
                 int height = (int) res.getDimension(android.R.dimen.notification_large_icon_height);
                 int width = (int) res.getDimension(android.R.dimen.notification_large_icon_width);
-                noti.setLargeIcon(Bitmap.createScaledBitmap(contact.getPhoto(), width, height, false));
+                mMessageNotificationBuilder.setLargeIcon(Bitmap.createScaledBitmap(contact.getPhoto(), width, height, false));
             }
             if (texts.size() == 1) {
                 TextMessage txt = texts.firstEntry().getValue();
                 txt.setNotified(true);
-                noti.setContentText(txt.getMessage());
-                noti.setStyle(null);
-                noti.setWhen(txt.getTimestamp());
+                mMessageNotificationBuilder.setContentText(txt.getMessage());
+                mMessageNotificationBuilder.setStyle(null);
+                mMessageNotificationBuilder.setWhen(txt.getTimestamp());
             } else {
                 NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
                 for (TextMessage s : texts.values()) {
                     inboxStyle.addLine(Html.fromHtml("<b>" + DateUtils.formatDateTime(this, s.getTimestamp(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL) + "</b> " + s.getMessage()));
                     s.setNotified(true);
                 }
-                noti.setContentText(texts.lastEntry().getValue().getMessage());
-                noti.setStyle(inboxStyle);
-                noti.setWhen(texts.lastEntry().getValue().getTimestamp());
+                mMessageNotificationBuilder.setContentText(texts.lastEntry().getValue().getMessage());
+                mMessageNotificationBuilder.setStyle(inboxStyle);
+                mMessageNotificationBuilder.setWhen(texts.lastEntry().getValue().getTimestamp());
             }
-            notificationManager.notify(c.notificationId, noti.build());
+            notificationManager.notify(c.getUuid(), mMessageNotificationBuilder.build());
         }
     }
 
@@ -1472,7 +1472,7 @@
                         conversation = startConversation(findContactByNumber(txt.getNumberUri()));
                         txt.setContact(conversation.getContact());
                     }
-                    if (conversation.mVisible) {
+                    if (conversation.isVisible()) {
                         txt.read();
                     }
 
@@ -1481,7 +1481,7 @@
                     ((HistoryServiceImpl) mHistoryService).insertNewTextMessage(txt);
 
                     conversation.addTextMessage(txt);
-                    if (!conversation.mVisible) {
+                    if (!conversation.isVisible()) {
                         updateTextNotifications();
                     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java b/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java
new file mode 100644
index 0000000..e7533b4
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java
@@ -0,0 +1,111 @@
+package cx.ring.utils;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+
+import cx.ring.R;
+import cx.ring.adapters.NumberAdapter;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conversation;
+
+/**
+ * Copyright (C) 2016 by Savoir-faire Linux
+ * Author : Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ * <p>
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * <p>
+ * This library 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
+ * Lesser General Public License for more details.
+ * *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+public class ActionHelper {
+
+    private static final String TAG = ActionHelper.class.getSimpleName();
+
+    public static AlertDialog launchDeleteAction(final Activity activity,
+                                                 final Conversation conversation,
+                                                 final Conversation.ConversationActionCallback callback) {
+        if (activity == null) {
+            Log.d(TAG, "launchDeleteAction: activity is null");
+            return null;
+        }
+
+        if (conversation == null) {
+            Log.d(TAG, "launchDeleteAction: conversation is null");
+            return null;
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(R.string.conversation_action_delete_this_title)
+                .setMessage(R.string.conversation_action_delete_this_message)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        if (callback != null) {
+                            callback.deleteConversation(conversation);
+                        }
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        /* Terminate with no action */
+                    }
+                });
+
+        AlertDialog alertDialog = builder.create();
+        alertDialog.show();
+        return alertDialog;
+    }
+
+    public static void launchCopyNumberToClipboardFromContact(final Activity activity,
+                                                              final CallContact callContact,
+                                                              final Conversation.ConversationActionCallback callback) {
+        if (callContact == null) {
+            Log.d(TAG, "launchCopyNumberToClipboardFromContact: callContact is null");
+            return;
+        }
+
+        if (activity == null) {
+            Log.d(TAG, "launchCopyNumberToClipboardFromContact: activity is null");
+            return;
+        }
+
+        if (callContact.getPhones().isEmpty()) {
+            Log.d(TAG, "launchCopyNumberToClipboardFromContact: no number to copy");
+            return;
+        } else if (callContact.getPhones().size() == 1 && callback != null) {
+            String number = callContact.getPhones().get(0).getNumber().toString();
+            callback.copyContactNumberToClipboard(number);
+            return;
+        }
+
+        final NumberAdapter adapter = new NumberAdapter(activity, callContact, true);
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(R.string.conversation_action_select_peer_number);
+        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                if (callback != null) {
+                    CallContact.Phone selectedPhone = (CallContact.Phone) adapter.getItem(which);
+                    callback.copyContactNumberToClipboard(selectedPhone.getNumber().toString());
+                }
+            }
+        });
+        AlertDialog dialog = builder.create();
+        final int listViewSidePadding = (int) activity
+                .getResources()
+                .getDimension(R.dimen.alert_dialog_side_padding_list_view);
+        dialog.getListView().setPadding(listViewSidePadding, 0, listViewSidePadding, 0);
+        dialog.show();
+    }
+}