rebranding: rename sflphone to ring

Refs #69972
Refs #70084

Change-Id: I152468471b02ff3b118c083cd285786e9e286fcc
diff --git a/ring-android/src/cx/ring/adapters/AccountSelectionAdapter.java b/ring-android/src/cx/ring/adapters/AccountSelectionAdapter.java
new file mode 100644
index 0000000..9b480bf
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/AccountSelectionAdapter.java
@@ -0,0 +1,179 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import cx.ring.R;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import cx.ring.model.account.Account;
+
+public class AccountSelectionAdapter extends BaseAdapter {
+
+    private static final String TAG = AccountSelectionAdapter.class.getSimpleName();
+
+    ArrayList<Account> accounts;
+    Context mContext;
+    int selectedAccount = -1;
+    static final String DEFAULT_ACCOUNT_ID = "IP2IP";
+
+    public AccountSelectionAdapter(Context cont, ArrayList<Account> newList) {
+        super();
+        accounts = newList;
+        mContext = cont;
+    }
+
+    @Override
+    public int getCount() {
+        return accounts.size();
+    }
+
+    @Override
+    public Account getItem(int pos) {
+        return accounts.get(pos);
+    }
+
+    @Override
+    public long getItemId(int pos) {
+        return 0;
+    }
+
+    @Override
+    public View getView(int pos, View convertView, ViewGroup parent) {
+        View rowView = convertView;
+        AccountView entryView = null;
+
+        if (rowView == null) {
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            rowView = inflater.inflate(R.layout.item_account, null);
+
+            entryView = new AccountView();
+            entryView.alias = (TextView) rowView.findViewById(R.id.account_alias);
+            entryView.host = (TextView) rowView.findViewById(R.id.account_host);
+            entryView.select = (ImageView) rowView.findViewById(R.id.account_selected);
+            rowView.setTag(entryView);
+        } else {
+            entryView = (AccountView) rowView.getTag();
+        }
+
+        entryView.alias.setText(accounts.get(pos).getAlias());
+
+        entryView.host.setText(accounts.get(pos).getHost() + " - " + accounts.get(pos).getRegistered_state());
+        // accManager.displayAccountDetails(accounts.get(pos), entryView);
+        if (pos == selectedAccount) {
+            entryView.select.setVisibility(View.VISIBLE);
+        } else {
+            entryView.select.setVisibility(View.GONE);
+        }
+
+        return rowView;
+    }
+
+    public Account getAccount(String accountID) {
+        for(Account acc : accounts) {
+            if(acc.getAccountID().contentEquals(accountID))
+                return acc;
+        }
+        return null;
+    }
+
+    /*********************
+     * ViewHolder Pattern
+     *********************/
+    public class AccountView {
+        public TextView alias;
+        public TextView host;
+        public ImageView select;
+    }
+
+    public void setSelectedAccount(int pos) {
+        selectedAccount = pos;
+    }
+
+    public Account getSelectedAccount() {
+        if (selectedAccount == -1) {
+            return null;
+        }
+        return accounts.get(selectedAccount);
+    }
+
+    public void removeAll() {
+        accounts.clear();
+        notifyDataSetChanged();
+
+    }
+
+    public void addAll(ArrayList<Account> results) {
+        accounts.addAll(results);
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Modify state of specific account
+     */
+    public void updateAccount(String accoundID, String state, int code) {
+        Log.i(TAG, "updateAccount");
+
+        for (Account a : accounts) {
+            if (a.getAccountID().contentEquals(accoundID)) {
+                a.setRegistered_state(state);
+                notifyDataSetChanged();
+                return;
+            }
+        }
+    }
+
+    public String getAccountOrder() {
+        String result = DEFAULT_ACCOUNT_ID + File.separator;
+        String selectedID = accounts.get(selectedAccount).getAccountID();
+        result += selectedID + File.separator;
+
+        for (Account a : accounts) {
+            if (a.getAccountID().contentEquals(selectedID)) {
+                continue;
+            }
+            result += a.getAccountID() + File.separator;
+        }
+
+        return result;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/adapters/ContactPictureTask.java b/ring-android/src/cx/ring/adapters/ContactPictureTask.java
new file mode 100644
index 0000000..5596a06
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/ContactPictureTask.java
@@ -0,0 +1,167 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.io.InputStream;
+
+import cx.ring.R;
+import cx.ring.model.CallContact;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.widget.ImageView;
+
+public class ContactPictureTask implements Runnable {
+    private ImageView view;
+    private CallContact contact;
+    private ContentResolver cr;
+    private static int PADDING = 5;
+
+    // private final String TAG = ContactPictureTask.class.getSimpleName();
+
+    public ContactPictureTask(Context context, ImageView element, CallContact item) {
+        contact = item;
+        cr = context.getContentResolver();
+        view = element;
+    }
+
+    public static Bitmap loadContactPhoto(ContentResolver cr, long id) {
+        if(id == -1)
+            return null;
+        Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
+        InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(cr, uri, true);
+        if (input == null) {
+            return null;
+        }
+        return BitmapFactory.decodeStream(input);
+    }
+
+    @Override
+    public void run() {
+        Bitmap photo_bmp;
+        try {
+            photo_bmp = loadContactPhoto(cr, contact.getId());
+        } catch (IllegalArgumentException e) {
+            photo_bmp = null;
+        }
+
+        int dpiPadding = (int) (PADDING * view.getResources().getDisplayMetrics().density);
+
+        if (photo_bmp == null) {
+            photo_bmp = decodeSampledBitmapFromResource(view.getResources(), R.drawable.ic_contact_picture, view.getWidth(), view.getHeight());
+        }
+
+        int w = photo_bmp.getWidth(), h = photo_bmp.getHeight();
+        if (w > h) {
+            w = h;
+        } else if (h > w) {
+            h = w;
+        }
+
+        final Bitmap externalBMP = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+
+        BitmapShader shader;
+        shader = new BitmapShader(photo_bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setShader(shader);
+        Canvas internalCanvas = new Canvas(externalBMP);
+
+        Paint paintLine = new Paint();
+        paintLine.setAntiAlias(true);
+        paintLine.setDither(true);
+        paintLine.setStyle(Style.STROKE);
+        paintLine.setColor(Color.WHITE);
+        // internalCanvas.drawCircle(externalBMP.getWidth() / 2, externalBMP.getHeight() / 2, externalBMP.getWidth() / 2 - dpiPadding / 2, paintLine);
+        // internalCanvas.drawOval(new RectF(PADDING, PADDING, externalBMP.getWidth() - dpiPadding, externalBMP.getHeight() - dpiPadding), paint);
+        internalCanvas.drawOval(new RectF(0, 0, externalBMP.getWidth(), externalBMP.getHeight()), paint);
+
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                view.setImageBitmap(externalBMP);
+                contact.setPhoto(externalBMP);
+                view.invalidate();
+            }
+        });
+    }
+
+    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
+
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        // options.inJustDecodeBounds = true;
+        // BitmapFactory.decodeResource(res, resId, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeResource(res, resId, options);
+    }
+
+    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            // Calculate ratios of height and width to requested height and width
+            final int heightRatio = Math.round((float) height / (float) reqHeight);
+            final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+            // Choose the smallest ratio as inSampleSize value, this will guarantee
+            // a final image with both dimensions larger than or equal to the
+            // requested height and width.
+            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+        }
+
+        return inSampleSize;
+    }
+}
diff --git a/ring-android/src/cx/ring/adapters/ContactsAdapter.java b/ring-android/src/cx/ring/adapters/ContactsAdapter.java
new file mode 100644
index 0000000..de47d94
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/ContactsAdapter.java
@@ -0,0 +1,297 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import cx.ring.R;
+import cx.ring.fragments.ContactListFragment;
+import cx.ring.model.CallContact;
+import cx.ring.views.stickylistheaders.StickyListHeadersAdapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAdapter, SectionIndexer {
+
+    private ExecutorService infos_fetcher = Executors.newCachedThreadPool();
+    Context mContext;
+
+    private ArrayList<CallContact> mContacts;
+    private int[] mSectionIndices;
+    private Character[] mSectionLetters;
+    WeakReference<ContactListFragment> parent;
+    private LayoutInflater mInflater;
+
+    // private static final String TAG = ContactsAdapter.class.getSimpleName();
+
+    public ContactsAdapter(ContactListFragment contactListFragment) {
+        super();
+        mContext = contactListFragment.getActivity();
+        mInflater = LayoutInflater.from(mContext);
+        parent = new WeakReference<ContactListFragment>(contactListFragment);
+        mContacts = new ArrayList<CallContact>();
+        mSectionIndices = getSectionIndices();
+        mSectionLetters = getSectionLetters();
+    }
+
+    public static final int TYPE_HEADER = 0;
+    public static final int TYPE_CONTACT = 1;
+
+    private int[] getSectionIndices() {
+        ArrayList<Integer> sectionIndices = new ArrayList<Integer>();
+        if (mContacts.isEmpty())
+            return new int[0];
+        char lastFirstChar = mContacts.get(0).getmDisplayName().charAt(0);
+        sectionIndices.add(0);
+        for (int i = 1; i < mContacts.size(); i++) {
+            if (mContacts.get(i).getmDisplayName().charAt(0) != lastFirstChar) {
+                lastFirstChar = mContacts.get(i).getmDisplayName().charAt(0);
+                sectionIndices.add(i);
+            }
+        }
+        int[] sections = new int[sectionIndices.size()];
+        for (int i = 0; i < sectionIndices.size(); i++) {
+            sections[i] = sectionIndices.get(i);
+        }
+        return sections;
+    }
+
+    private Character[] getSectionLetters() {
+        Character[] letters = new Character[mSectionIndices.length];
+        for (int i = 0; i < mSectionIndices.length; i++) {
+            letters[i] = mContacts.get(mSectionIndices[i]).getmDisplayName().charAt(0);
+        }
+        return letters;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup root) {
+        ContactView entryView;
+
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.item_contact, null);
+
+            entryView = new ContactView();
+            entryView.quick_starred = (ImageButton) convertView.findViewById(R.id.quick_starred);
+            entryView.quick_edit = (ImageButton) convertView.findViewById(R.id.quick_edit);
+            entryView.quick_discard = (ImageButton) convertView.findViewById(R.id.quick_discard);
+            entryView.quick_call = (ImageButton) convertView.findViewById(R.id.quick_call);
+            entryView.quick_msg = (ImageButton) convertView.findViewById(R.id.quick_message);
+            entryView.photo = (ImageView) convertView.findViewById(R.id.photo);
+            entryView.display_name = (TextView) convertView.findViewById(R.id.display_name);
+            convertView.setTag(entryView);
+        } else {
+            entryView = (ContactView) convertView.getTag();
+        }
+
+        final CallContact item = mContacts.get(position);
+
+        entryView.display_name.setText(item.getmDisplayName());
+
+        if (item.hasPhoto()) {
+            entryView.photo.setImageBitmap(item.getPhoto());
+        } else {
+            infos_fetcher.execute(new ContactPictureTask(mContext, entryView.photo, item));
+        }
+
+        entryView.quick_call.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                parent.get().mCallbacks.onCallContact(item);
+
+            }
+        });
+
+        entryView.quick_msg.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                parent.get().mCallbacks.onTextContact(item);
+            }
+        });
+
+        entryView.quick_starred.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                Toast.makeText(mContext, "Coming soon", Toast.LENGTH_SHORT).show();
+            }
+        });
+
+        entryView.quick_edit.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                parent.get().mCallbacks.onEditContact(item);
+
+            }
+        });
+
+        entryView.quick_discard.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                Toast.makeText(mContext, "Coming soon", Toast.LENGTH_SHORT).show();
+
+            }
+        });
+
+        entryView.quick_edit.setClickable(false);
+        entryView.quick_discard.setClickable(false);
+        entryView.quick_starred.setClickable(false);
+
+        return convertView;
+    }
+
+    /*********************
+     * ViewHolder Pattern
+     *********************/
+    public class ContactView {
+        ImageButton quick_starred, quick_edit, quick_discard, quick_call, quick_msg;
+        ImageView photo;
+        TextView display_name;
+    }
+
+    @Override
+    public int getCount() {
+        return mContacts.size();
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 2;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public View getHeaderView(int position, View convertView, ViewGroup parent) {
+        HeaderViewHolder holder;
+
+        if (convertView == null) {
+            holder = new HeaderViewHolder();
+            convertView = mInflater.inflate(R.layout.header, parent, false);
+            holder.text = (TextView) convertView.findViewById(R.id.header_letter);
+            convertView.setTag(holder);
+        } else {
+            holder = (HeaderViewHolder) convertView.getTag();
+        }
+
+        // set header text as first char in name
+        char headerChar = mContacts.get(position).getmDisplayName().subSequence(0, 1).charAt(0);
+
+        holder.text.setText("" + headerChar);
+
+        return convertView;
+
+    }
+
+    class HeaderViewHolder {
+        TextView text;
+    }
+
+    @Override
+    public long getHeaderId(int position) {
+        // return the first character of the name as ID because this is what
+        // headers are based upon
+        return mContacts.get(position).getmDisplayName().subSequence(0, 1).charAt(0);
+    }
+
+    @Override
+    public int getPositionForSection(int section) {
+        if (section >= mSectionIndices.length) {
+            section = mSectionIndices.length - 1;
+        } else if (section < 0) {
+            section = 0;
+        }
+        return mSectionIndices[section];
+    }
+
+    @Override
+    public int getSectionForPosition(int position) {
+        for (int i = 0; i < mSectionIndices.length; i++) {
+            if (position < mSectionIndices[i]) {
+                return i - 1;
+            }
+        }
+        return mSectionIndices.length - 1;
+    }
+
+    @Override
+    public Object[] getSections() {
+        return mSectionLetters;
+    }
+
+    @Override
+    public CallContact getItem(int position) {
+        return mContacts.get(position);
+    }
+
+    public void clear() {
+        mContacts = new ArrayList<CallContact>();
+        mSectionIndices = new int[0];
+        mSectionLetters = new Character[0];
+        notifyDataSetChanged();
+    }
+
+    public void restore() {
+        mContacts = new ArrayList<CallContact>();
+        mSectionIndices = getSectionIndices();
+        mSectionLetters = getSectionLetters();
+        notifyDataSetChanged();
+    }
+
+    public void addAll(ArrayList<CallContact> tmp) {
+        mContacts.addAll(tmp);
+        mSectionIndices = getSectionIndices();
+        mSectionLetters = getSectionLetters();
+        notifyDataSetChanged();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/adapters/DiscussArrayAdapter.java b/ring-android/src/cx/ring/adapters/DiscussArrayAdapter.java
new file mode 100644
index 0000000..9cdfc41
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/DiscussArrayAdapter.java
@@ -0,0 +1,106 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cx.ring.R;
+import cx.ring.model.SipMessage;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class DiscussArrayAdapter extends BaseAdapter {
+
+    private TextView countryName;
+    private List<SipMessage> messages = new ArrayList<SipMessage>();
+    private LinearLayout wrapper;
+    private Context mContext;
+
+    public DiscussArrayAdapter(Context context, Bundle args) {
+        mContext = context;
+        
+        if(args == null)
+            messages = new ArrayList<SipMessage>();
+        else
+            messages = args.getParcelableArrayList("messages");
+        
+    }
+
+    public void add(SipMessage object) {
+        messages.add(object);
+        notifyDataSetChanged();
+    }
+
+    public int getCount() {
+        return this.messages.size();
+    }
+
+    public SipMessage getItem(int index) {
+        return this.messages.get(index);
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View row = convertView;
+        if (row == null) {
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            row = inflater.inflate(R.layout.item_message, parent, false);
+        }
+
+        wrapper = (LinearLayout) row.findViewById(R.id.wrapper);
+
+        SipMessage coment = getItem(position);
+
+        countryName = (TextView) row.findViewById(R.id.comment);
+
+        countryName.setText(coment.comment);
+
+        countryName.setBackgroundResource(coment.left ? R.drawable.bubble_left_selector : R.drawable.bubble_right_selector);
+        wrapper.setGravity(coment.left ? Gravity.LEFT : Gravity.RIGHT);
+
+        return row;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/adapters/SectionsPagerAdapter.java b/ring-android/src/cx/ring/adapters/SectionsPagerAdapter.java
new file mode 100644
index 0000000..e9a4e15
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/SectionsPagerAdapter.java
@@ -0,0 +1,129 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import cx.ring.R;
+import cx.ring.fragments.CallListFragment;
+import cx.ring.fragments.DialingFragment;
+import cx.ring.fragments.HistoryFragment;
+import cx.ring.views.PagerSlidingTabStrip;
+
+import android.content.Context;
+import android.util.Log;
+
+public class SectionsPagerAdapter extends android.support.v4.app.FragmentStatePagerAdapter implements PagerSlidingTabStrip.IconTabProvider {
+
+    private static final String TAG = SectionsPagerAdapter.class.getSimpleName();
+    Context mContext;
+    ArrayList<Fragment> fragments;
+
+    public SectionsPagerAdapter(Context c, FragmentManager fm) {
+        super(fm);
+        mContext = c;
+        fragments = new ArrayList<Fragment>();
+        fragments.add(new DialingFragment());
+        fragments.add(new CallListFragment());
+        fragments.add(new HistoryFragment());
+    }
+
+    @Override
+    public Fragment getItem(int i) {
+
+        return fragments.get(i);
+    }
+
+    public String getClassName(int i) {
+        String name;
+
+        switch (i) {
+        case 0:
+            name = DialingFragment.class.getName();
+            break;
+        case 1:
+            name = CallListFragment.class.getName();
+            break;
+        case 2:
+            name = HistoryFragment.class.getName();
+            break;
+
+        default:
+            Log.e(TAG, "getClassName: unknown fragment position " + i);
+            return null;
+        }
+
+        // Log.w(TAG, "getClassName: name=" + name);
+        return name;
+    }
+
+    @Override
+    public int getCount() {
+        return fragments.size();
+    }
+
+    @Override
+    public CharSequence getPageTitle(int position) {
+
+        switch (position) {
+        case 0:
+            return mContext.getString(R.string.title_section0).toUpperCase(Locale.getDefault());
+        case 1:
+            return mContext.getString(R.string.title_section1).toUpperCase(Locale.getDefault());
+        case 2:
+            return mContext.getString(R.string.title_section2).toUpperCase(Locale.getDefault());
+        default:
+            Log.e(TAG, "getPageTitle: unknown tab position " + position);
+            break;
+        }
+        return null;
+    }
+
+    @Override
+    public int getPageIconResId(int position) {
+        switch (position) {
+        case 0:
+            return R.drawable.ic_action_dial_pad_light;
+        case 1:
+            return R.drawable.ic_action_call;
+        case 2:
+            return R.drawable.ic_action_time;
+        default:
+            Log.e(TAG, "getPageTitle: unknown tab position " + position);
+            break;
+        }
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/adapters/StarredContactsAdapter.java b/ring-android/src/cx/ring/adapters/StarredContactsAdapter.java
new file mode 100644
index 0000000..6690341
--- /dev/null
+++ b/ring-android/src/cx/ring/adapters/StarredContactsAdapter.java
@@ -0,0 +1,110 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.adapters;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import cx.ring.R;
+import cx.ring.model.CallContact;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class StarredContactsAdapter extends BaseAdapter {
+
+    private ExecutorService infos_fetcher = Executors.newCachedThreadPool();
+    private ArrayList<CallContact> dataset;
+    Context mContext;
+
+//    private static final String TAG = ContactsAdapter.class.getSimpleName();
+
+    public StarredContactsAdapter(Context context) {
+        super();
+        mContext = context;
+        dataset = new ArrayList<CallContact>();
+    }
+
+    public void removeAll() {
+        dataset.clear();
+        notifyDataSetChanged();
+    }
+
+    public void addAll(ArrayList<CallContact> arrayList) {
+        dataset.addAll(arrayList);
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getCount() {
+        return dataset.size();
+    }
+
+    @Override
+    public CallContact getItem(int index) {
+        return dataset.get(index);
+    }
+
+    @Override
+    public long getItemId(int index) {
+        return dataset.get(index).getId();
+    }
+
+    @Override
+    public View getView(int pos, View convView, ViewGroup parent) {
+
+        View v = convView;
+        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        if (v == null) {
+            v = inflater.inflate(R.layout.item_contact_starred, null);
+        }
+
+        CallContact item = dataset.get(pos);
+
+        ((TextView) v.findViewById(R.id.display_name)).setText(item.getmDisplayName());
+        ImageView photo_view = (ImageView) v.findViewById(R.id.photo);
+
+        if(item.hasPhoto()){
+            photo_view.setImageBitmap(item.getPhoto());
+        } else {
+            infos_fetcher.execute(new ContactPictureTask(mContext, photo_view, item));
+        }
+        return v;
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/client/AccountEditionActivity.java b/ring-android/src/cx/ring/client/AccountEditionActivity.java
new file mode 100644
index 0000000..6433049
--- /dev/null
+++ b/ring-android/src/cx/ring/client/AccountEditionActivity.java
@@ -0,0 +1,333 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *          Alexandre Savard <alexandre.savard@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.client;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.*;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.v13.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import cx.ring.R;
+import cx.ring.fragments.AdvancedAccountFragment;
+import cx.ring.fragments.AudioManagementFragment;
+import cx.ring.fragments.NestedSettingsFragment;
+import cx.ring.fragments.SecurityAccountFragment;
+import cx.ring.model.account.Account;
+import cx.ring.service.ISipService;
+import cx.ring.service.SipService;
+import cx.ring.views.PagerSlidingTabStrip;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Observable;
+import java.util.Observer;
+
+import cx.ring.fragments.GeneralAccountFragment;
+
+public class AccountEditionActivity extends Activity implements GeneralAccountFragment.Callbacks, AudioManagementFragment.Callbacks,
+        AdvancedAccountFragment.Callbacks, SecurityAccountFragment.Callbacks, NestedSettingsFragment.Callbacks {
+    private static final String TAG = AccountEditionActivity.class.getSimpleName();
+
+    private boolean mBound = false;
+    private ISipService service;
+    private Account acc_selected;
+
+    private NestedSettingsFragment toDisplay;
+
+    private Observer mAccountObserver = new Observer() {
+
+        @Override
+        public void update(Observable observable, Object data) {
+            processAccount();
+        }
+    };
+
+    PreferencesPagerAdapter mPreferencesPagerAdapter;
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            service = ISipService.Stub.asInterface(binder);
+            mBound = true;
+
+            ArrayList<Fragment> fragments = new ArrayList<Fragment>();
+            if (acc_selected.isIP2IP()) {
+                fragments.add(new AudioManagementFragment());
+            } else {
+                fragments.add(new GeneralAccountFragment());
+                fragments.add(new AudioManagementFragment());
+				if(acc_selected.isSip())
+				{
+					fragments.add(new AdvancedAccountFragment());
+					fragments.add(new SecurityAccountFragment());
+				}
+            }
+
+            ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
+
+            mPreferencesPagerAdapter = new PreferencesPagerAdapter(AccountEditionActivity.this, getFragmentManager(), fragments);
+            mViewPager.setAdapter(mPreferencesPagerAdapter);
+            mViewPager.setOffscreenPageLimit(3);
+
+            final PagerSlidingTabStrip strip = PagerSlidingTabStrip.class.cast(findViewById(R.id.pager_sliding_strip));
+
+            strip.setViewPager(mViewPager);
+
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_account_settings);
+
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+
+        acc_selected = getIntent().getExtras().getParcelable("account");
+
+        acc_selected.addObserver(mAccountObserver);
+
+        if (!mBound) {
+            Log.i(TAG, "onCreate: Binding service...");
+            Intent intent = new Intent(this, SipService.class);
+            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        }
+
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        if (acc_selected.isIP2IP()) {
+            return true;
+        }
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.account_edition, menu);
+        return true;
+    }
+
+    @Override
+    public void onBackPressed() {
+
+        if (toDisplay != null) {
+            getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slidein_up, R.animator.slideout_down).remove(toDisplay).commit();
+            ((SecurityAccountFragment) mPreferencesPagerAdapter.getItem(3)).updateSummaries();
+            toDisplay = null;
+            return;
+        }
+
+        if (acc_selected.isIP2IP()) {
+            super.onBackPressed();
+            return;
+        }
+
+        super.onBackPressed();
+
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+
+        switch (item.getItemId()) {
+        case android.R.id.home:
+            if (toDisplay != null) {
+                getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slidein_up, R.animator.slideout_down).remove(toDisplay)
+                        .commit();
+                ((SecurityAccountFragment) mPreferencesPagerAdapter.getItem(3)).updateSummaries();
+                toDisplay = null;
+            } else
+                finish();
+            return true;
+        case R.id.menuitem_delete:
+            AlertDialog dialog = createDeleteDialog();
+            dialog.show();
+            break;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    private void processAccount() {
+        try {
+            service.setCredentials(acc_selected.getAccountID(), acc_selected.getCredentialsHashMapList());
+            service.setAccountDetails(acc_selected.getAccountID(), acc_selected.getDetails());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    private AlertDialog createDeleteDialog() {
+        Activity ownerActivity = this;
+        AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+        builder.setMessage("Do you really want to delete this account").setTitle("Delete Account")
+                .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        Bundle bundle = new Bundle();
+                        bundle.putString("AccountID", acc_selected.getAccountID());
+
+                        try {
+                            service.removeAccount(acc_selected.getAccountID());
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                        finish();
+                    }
+                }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        /* Terminate with no action */
+                    }
+                });
+
+        AlertDialog alertDialog = builder.create();
+        alertDialog.setOwnerActivity(ownerActivity);
+
+        return alertDialog;
+    }
+
+    public class PreferencesPagerAdapter extends FragmentStatePagerAdapter {
+
+        Context mContext;
+        ArrayList<Fragment> fragments;
+
+        public PreferencesPagerAdapter(Context c, FragmentManager fm, ArrayList<Fragment> items) {
+            super(fm);
+            mContext = c;
+            fragments = new ArrayList<Fragment>(items);
+
+        }
+
+        @Override
+        public int getCount() {
+            return fragments.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            return fragments.get(position);
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            switch (position) {
+            case 0:
+                if (acc_selected.isIP2IP()) {
+                    return getString(R.string.account_preferences_audio_tab).toUpperCase(Locale.getDefault());
+                } else {
+                    return getString(R.string.account_preferences_basic_tab).toUpperCase(Locale.getDefault());
+                }
+            case 1:
+                return getString(R.string.account_preferences_audio_tab).toUpperCase(Locale.getDefault());
+            case 2:
+                return getString(R.string.account_preferences_advanced_tab).toUpperCase(Locale.getDefault());
+            case 3:
+                return getString(R.string.account_preferences_security_tab).toUpperCase(Locale.getDefault());
+            default:
+                Log.e(TAG, "getPreferencePageTitle: unknown tab position " + position);
+                break;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public ISipService getService() {
+        return service;
+    }
+
+    @Override
+    public Account getAccount() {
+        return acc_selected;
+    }
+
+    @Override
+    public void displayCredentialsScreen() {
+        toDisplay = new NestedSettingsFragment();
+        Bundle b = new Bundle();
+        b.putInt("MODE", 0);
+        toDisplay.setArguments(b);
+        getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slidein_up, R.animator.slideout_down)
+                .replace(R.id.hidden_container, toDisplay).commit();
+    }
+
+    @Override
+    public void displaySRTPScreen() {
+        toDisplay = new NestedSettingsFragment();
+        Bundle b = new Bundle();
+        b.putInt("MODE", 1);
+        toDisplay.setArguments(b);
+        getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slidein_up, R.animator.slideout_down)
+                .replace(R.id.hidden_container, toDisplay).commit();
+    }
+
+    @Override
+    public void displayTLSScreen() {
+        toDisplay = new NestedSettingsFragment();
+        Bundle b = new Bundle();
+        b.putInt("MODE", 2);
+        toDisplay.setArguments(b);
+        getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slidein_up, R.animator.slideout_down)
+                .replace(R.id.hidden_container, toDisplay).commit();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/client/AccountWizard.java b/ring-android/src/cx/ring/client/AccountWizard.java
new file mode 100644
index 0000000..43cd2ba
--- /dev/null
+++ b/ring-android/src/cx/ring/client/AccountWizard.java
@@ -0,0 +1,175 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.client;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v13.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.MenuItem;
+import cx.ring.R;
+import cx.ring.fragments.AccountCreationFragment;
+import cx.ring.service.ISipService;
+import cx.ring.service.SipService;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class AccountWizard extends Activity implements AccountCreationFragment.Callbacks {
+    static final String TAG = "AccountWizard";
+    private boolean mBound = false;
+    private ISipService service;
+    ViewPager mViewPager;
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            service = ISipService.Stub.asInterface(binder);
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_wizard);
+        mViewPager = (ViewPager) findViewById(R.id.pager);
+
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+        getActionBar().setHomeButtonEnabled(true);
+        SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(AccountWizard.this, getFragmentManager());
+        mViewPager.setAdapter(mSectionsPagerAdapter);
+
+        if (!mBound) {
+            Log.i(TAG, "onCreate: Binding service...");
+            Intent intent = new Intent(this, SipService.class);
+            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        }
+
+    }
+
+    /* activity finishes itself or is being killed by the system */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case android.R.id.home:
+            finish();
+            return true;
+        default:
+            return true;
+        }
+    }
+
+    public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
+
+        Context mContext;
+        ArrayList<Fragment> fragments;
+
+        public SectionsPagerAdapter(Context c, FragmentManager fm) {
+            super(fm);
+            mContext = c;
+            fragments = new ArrayList<Fragment>();
+            fragments.add(new AccountCreationFragment());
+
+        }
+
+        @Override
+        public Fragment getItem(int i) {
+
+            return fragments.get(i);
+        }
+
+        public String getClassName(int i) {
+            String name;
+
+            switch (i) {
+            case 0:
+                name = AccountCreationFragment.class.getName();
+                break;
+
+            default:
+                Log.e(TAG, "getClassName: unknown fragment position " + i);
+                return null;
+            }
+
+            // Log.w(TAG, "getClassName: name=" + name);
+            return name;
+        }
+
+        @Override
+        public int getCount() {
+            return 1;
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            switch (position) {
+            case 0:
+                return mContext.getString(R.string.title_section0).toUpperCase(Locale.getDefault());
+            default:
+                Log.e(TAG, "getPageTitle: unknown tab position " + position);
+                break;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public ISipService getService() {
+        return service;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/client/CallActivity.java b/ring-android/src/cx/ring/client/CallActivity.java
new file mode 100644
index 0000000..8f664cd
--- /dev/null
+++ b/ring-android/src/cx/ring/client/CallActivity.java
@@ -0,0 +1,327 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.client;
+
+import java.util.*;
+
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import cx.ring.R;
+import cx.ring.fragments.CallFragment;
+import cx.ring.fragments.IMFragment;
+import cx.ring.model.account.Account;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+import cx.ring.model.SipMessage;
+import cx.ring.service.ISipService;
+import cx.ring.service.SipService;
+import cx.ring.utils.CallProximityManager;
+import cx.ring.views.CallPaneLayout;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.v4.widget.SlidingPaneLayout;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+public class CallActivity extends FragmentActivity implements IMFragment.Callbacks, CallFragment.Callbacks, CallProximityManager.ProximityDirector {
+
+    @SuppressWarnings("unused")
+    static final String TAG = "CallActivity";
+    private ISipService mService;
+    CallPaneLayout mSlidingPaneLayout;
+
+    IMFragment mIMFragment;
+    CallFragment mCurrentCallFragment;
+    private Conference mDisplayedConference;
+
+    /* result code sent in case of call failure */
+    public static int RESULT_FAILURE = -10;
+    private CallProximityManager mProximityManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_call_layout);
+
+        Window w = getWindow();
+        w.setFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+        setUpSlidingPanel();
+
+        mProximityManager = new CallProximityManager(this, this);
+        mProximityManager.startTracking();
+
+        mCurrentCallFragment = new CallFragment();
+        mIMFragment = new IMFragment();
+
+        if(!checkExternalCall()) {
+            mDisplayedConference = getIntent().getParcelableExtra("conference");
+            Bundle IMBundle = new Bundle();
+            if (getIntent().getBooleanExtra("resuming", false)) {
+                IMBundle.putParcelableArrayList("messages", mDisplayedConference.getMessages());
+                mIMFragment.setArguments(IMBundle);
+            } else {
+                IMBundle.putParcelableArrayList("messages", new ArrayList<SipMessage>());
+                mIMFragment.setArguments(IMBundle);
+            }
+        }
+
+        mSlidingPaneLayout.setCurFragment(mCurrentCallFragment);
+        getSupportFragmentManager().beginTransaction().replace(R.id.ongoingcall_pane, mCurrentCallFragment)
+                .replace(R.id.message_list_frame, mIMFragment).commit();
+    }
+
+    private void setUpSlidingPanel() {
+        mSlidingPaneLayout = (CallPaneLayout) findViewById(R.id.slidingpanelayout);
+        mSlidingPaneLayout.setParallaxDistance(500);
+        mSlidingPaneLayout.setSliderFadeColor(Color.TRANSPARENT);
+
+        mSlidingPaneLayout.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
+
+            @Override
+            public void onPanelSlide(View view, float offSet) {
+            }
+
+            @Override
+            public void onPanelOpened(View view) {
+                getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+            }
+
+            @Override
+            public void onPanelClosed(View view) {
+                mCurrentCallFragment.getBubbleView().restartDrawing();
+                getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+            }
+        });
+    }
+
+    @Override
+    public void onFragmentCreated() {
+        Intent intent = new Intent(this, SipService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        Window window = getWindow();
+        window.setFormat(PixelFormat.RGBA_8888);
+    }
+
+    private Handler mHandler = new Handler();
+    private Runnable mUpdateTimeTask = new Runnable() {
+        @Override
+        public void run() {
+            if (mCurrentCallFragment != null)
+                mCurrentCallFragment.updateTime();
+            mHandler.postAtTime(this, SystemClock.uptimeMillis() + 1000);
+        }
+    };
+
+    /* activity no more in foreground */
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mHandler.removeCallbacks(mUpdateTimeTask);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return super.onKeyUp(keyCode, event);
+        }
+        mCurrentCallFragment.onKeyUp(keyCode, event);
+        return true;
+    }
+
+    @Override
+    protected void onDestroy() {
+
+        unbindService(mConnection);
+
+        mProximityManager.stopTracking();
+        mProximityManager.release(0);
+
+        super.onDestroy();
+    }
+
+    /**
+     * Defines callbacks for service binding, passed to bindService()
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @SuppressWarnings("unchecked")
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            mService = ISipService.Stub.asInterface(binder);
+
+            if (mDisplayedConference.getState().contentEquals("NONE")) {
+                try {
+                    mService.placeCall(mDisplayedConference.getParticipants().get(0));
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+        }
+    };
+
+    private boolean checkExternalCall() {
+        Uri u = getIntent().getData();
+        if (u != null) {
+            CallContact c = CallContact.ContactBuilder.buildUnknownContact(u.getSchemeSpecificPart());
+            try {
+                String accountID = (String) mService.getAccountList().get(1); // We use the first account to place outgoing calls
+                HashMap<String, String> details = (HashMap<String, String>) mService.getAccountDetails(accountID);
+                ArrayList<HashMap<String, String>> credentials = (ArrayList<HashMap<String, String>>) mService.getCredentials(accountID);
+                Account acc = new Account(accountID, details, credentials);
+
+                Bundle args = new Bundle();
+                args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt())));
+                args.putParcelable(SipCall.ACCOUNT, acc);
+                args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE);
+                args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING);
+                args.putParcelable(SipCall.CONTACT, c);
+
+                mDisplayedConference = new Conference(Conference.DEFAULT_ID);
+                mDisplayedConference.getParticipants().add(new SipCall(args));
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public ISipService getService() {
+        return mService;
+    }
+
+    @Override
+    public Conference getDisplayedConference() {
+        return mDisplayedConference;
+    }
+
+    @Override
+    public void updateDisplayedConference(Conference c) {
+        if(mDisplayedConference.equals(c)){
+            mDisplayedConference = c;
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        Intent launchHome = new Intent(this, HomeActivity.class);
+        launchHome.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        launchHome.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        startActivity(launchHome);
+    }
+
+    @Override
+    public void terminateCall() {
+        mHandler.removeCallbacks(mUpdateTimeTask);
+        mCurrentCallFragment.getBubbleView().stopThread();
+        TimerTask quit = new TimerTask() {
+
+            @Override
+            public void run() {
+                finish();
+            }
+        };
+
+        new Timer().schedule(quit, 1000);
+    }
+
+    @Override
+    public boolean sendIM(SipMessage msg) {
+
+        try {
+            Log.i(TAG, "Sending:"+msg.comment+"to"+mDisplayedConference.getId());
+            mService.sendTextMessage(mDisplayedConference.getId(), msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void startTimer() {
+        mHandler.postDelayed(mUpdateTimeTask, 0);
+    }
+
+    @Override
+    public void slideChatScreen() {
+
+        if (mSlidingPaneLayout.isOpen()) {
+            mSlidingPaneLayout.closePane();
+        } else {
+            mCurrentCallFragment.getBubbleView().stopThread();
+            mSlidingPaneLayout.openPane();
+        }
+    }
+
+    @Override
+    public boolean shouldActivateProximity() {
+        return true;
+    }
+
+    @Override
+    public void onProximityTrackingChanged(boolean acquired) {
+        // TODO Stub de la méthode généré automatiquement
+
+    }
+}
diff --git a/ring-android/src/cx/ring/client/DetailHistoryActivity.java b/ring-android/src/cx/ring/client/DetailHistoryActivity.java
new file mode 100644
index 0000000..89625f4
--- /dev/null
+++ b/ring-android/src/cx/ring/client/DetailHistoryActivity.java
@@ -0,0 +1,136 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.client;
+
+import cx.ring.R;
+import cx.ring.fragments.DetailsHistoryEntryFragment;
+import cx.ring.fragments.HistoryFragment;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+import cx.ring.service.ISipService;
+import cx.ring.service.SipService;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.MenuItem;
+
+public class DetailHistoryActivity extends Activity implements DetailsHistoryEntryFragment.Callbacks {
+
+    private boolean mBound = false;
+    private ISipService service;
+    private String TAG = DetailHistoryActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_holder);
+
+        Intent intent = new Intent(this, SipService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case android.R.id.home:
+            finish();
+            return true;
+        default:
+            return true;
+        }
+    }
+
+    @Override
+    public ISipService getService() {
+        return service;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            service = ISipService.Stub.asInterface(binder);
+
+            FragmentTransaction ft = getFragmentManager().beginTransaction();
+
+            Fragment fr = new DetailsHistoryEntryFragment();
+            fr.setArguments(getIntent().getBundleExtra(HistoryFragment.ARGS));
+            ft.replace(R.id.frag_container, fr);
+
+            ft.commit();
+
+            mBound = true;
+            Log.d(TAG, "Service connected service=" + service);
+
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+
+            mBound = false;
+            Log.d(TAG, "Service disconnected service=" + service);
+        }
+    };
+
+    @Override
+    public void onCall(SipCall call) {
+        Bundle bundle = new Bundle();
+        Conference tmp = new Conference(Conference.DEFAULT_ID);
+
+        tmp.getParticipants().add(call);
+
+        bundle.putParcelable("conference", tmp);
+        Intent intent = new Intent().setClass(this, CallActivity.class);
+        intent.putExtra("resuming", false);
+        intent.putExtras(bundle);
+        startActivity(intent);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/client/HomeActivity.java b/ring-android/src/cx/ring/client/HomeActivity.java
new file mode 100644
index 0000000..fdcb022
--- /dev/null
+++ b/ring-android/src/cx/ring/client/HomeActivity.java
@@ -0,0 +1,651 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@gmail.com>
+ *          Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.client;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.support.v4.app.*;
+import cx.ring.R;
+import cx.ring.fragments.AboutFragment;
+import cx.ring.fragments.AccountsManagementFragment;
+import cx.ring.fragments.CallListFragment;
+import cx.ring.fragments.ContactListFragment;
+import cx.ring.fragments.DialingFragment;
+import cx.ring.fragments.HistoryFragment;
+import cx.ring.fragments.HomeFragment;
+import cx.ring.fragments.MenuFragment;
+import cx.ring.history.HistoryEntry;
+import cx.ring.model.account.Account;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+import cx.ring.service.ISipService;
+import cx.ring.service.SipService;
+import cx.ring.views.SlidingUpPanelLayout;
+import cx.ring.views.SlidingUpPanelLayout.PanelSlideListener;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+public class HomeActivity extends FragmentActivity implements DialingFragment.Callbacks, AccountsManagementFragment.Callbacks,
+        ContactListFragment.Callbacks, CallListFragment.Callbacks, HistoryFragment.Callbacks, MenuFragment.Callbacks {
+
+    static final String TAG = HomeActivity.class.getSimpleName();
+
+    private ContactListFragment mContactsFragment = null;
+    private MenuFragment fMenu;
+
+    private boolean mBound = false;
+    private ISipService service;
+
+    public static final int REQUEST_CODE_PREFERENCES = 1;
+    public static final int REQUEST_CODE_CALL = 3;
+
+    SlidingUpPanelLayout mContactDrawer;
+    private DrawerLayout mNavigationDrawer;
+    private ActionBarDrawerToggle mDrawerToggle;
+
+    private boolean isClosing = false;
+    private Timer t = new Timer();
+
+    protected Fragment fContent;
+
+    /* called before activity is killed, e.g. rotation */
+    @Override
+    protected void onSaveInstanceState(Bundle bundle) {
+        super.onSaveInstanceState(bundle);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_home);
+
+        // Bind to LocalService
+        if (!mBound) {
+            Log.i(TAG, "onStart: Binding service...");
+            Intent intent = new Intent(this, SipService.class);
+            startService(intent);
+            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        }
+
+        mContactsFragment = new ContactListFragment();
+        getFragmentManager().beginTransaction().replace(R.id.contacts_frame, mContactsFragment).commit();
+
+        mContactDrawer = (SlidingUpPanelLayout) findViewById(R.id.contact_panel);
+        // mContactDrawer.setShadowDrawable(getResources().getDrawable(R.drawable.above_shadow));
+        mContactDrawer.setAnchorPoint(0.3f);
+        mContactDrawer.setDragView(findViewById(R.id.contacts_frame));
+        mContactDrawer.setEnableDragViewTouchEvents(true);
+        mContactDrawer.setPanelSlideListener(new PanelSlideListener() {
+
+            @Override
+            public void onPanelSlide(View panel, float slideOffset) {
+                if (slideOffset < 0.2) {
+                    if (getActionBar().isShowing()) {
+                        getActionBar().hide();
+                    }
+                } else {
+                    if (!getActionBar().isShowing()) {
+                        getActionBar().show();
+                    }
+                }
+            }
+
+            @Override
+            public void onPanelExpanded(View panel) {
+
+            }
+
+            @Override
+            public void onPanelCollapsed(View panel) {
+
+            }
+
+            @Override
+            public void onPanelAnchored(View panel) {
+
+            }
+        });
+
+        mNavigationDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+
+        // set a custom shadow that overlays the main content when the drawer opens
+        mNavigationDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+        getActionBar().setHomeButtonEnabled(true);
+
+        mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
+                mNavigationDrawer, /* DrawerLayout object */
+                R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
+                R.string.drawer_open, /* "open drawer" description for accessibility */
+                R.string.drawer_close /* "close drawer" description for accessibility */
+        ) {
+            @Override
+            public void onDrawerClosed(View view) {
+                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+            }
+
+            @Override
+            public void onDrawerOpened(View drawerView) {
+                // getActionBar().setTitle(mDrawerTitle);
+                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+            }
+        };
+
+        mNavigationDrawer.setDrawerListener(mDrawerToggle);
+
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        // Sync the toggle state after onRestoreInstanceState has occurred.
+        mDrawerToggle.syncState();
+        if (mContactDrawer.isExpanded()) {
+            getActionBar().hide();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDrawerToggle.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onStart() {
+        Log.i(TAG, "onStart");
+
+        if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean("installed", false)) {
+            PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit().putBoolean("installed", true).commit();
+
+            copyAssetFolder(getAssets(), "ringtones", getFilesDir().getAbsolutePath() + "/ringtones");
+        }
+
+        super.onStart();
+
+    }
+
+    private static boolean copyAssetFolder(AssetManager assetManager, String fromAssetPath, String toPath) {
+        try {
+            String[] files = assetManager.list(fromAssetPath);
+            new File(toPath).mkdirs();
+            Log.i(TAG, "Creating :" + toPath);
+            boolean res = true;
+            for (String file : files)
+                if (file.contains(".")) {
+                    Log.i(TAG, "Copying file :" + fromAssetPath + "/" + file + " to " + toPath + "/" + file);
+                    res &= copyAsset(assetManager, fromAssetPath + "/" + file, toPath + "/" + file);
+                } else {
+                    Log.i(TAG, "Copying folder :" + fromAssetPath + "/" + file + " to " + toPath + "/" + file);
+                    res &= copyAssetFolder(assetManager, fromAssetPath + "/" + file, toPath + "/" + file);
+                }
+            return res;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) {
+        InputStream in;
+        OutputStream out;
+        try {
+            in = assetManager.open(fromAssetPath);
+            new File(toPath).createNewFile();
+            out = new FileOutputStream(toPath);
+            copyFile(in, out);
+            in.close();
+            in = null;
+            out.flush();
+            out.close();
+            out = null;
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private static void copyFile(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        int read;
+        while ((read = in.read(buffer)) != -1) {
+            out.write(buffer, 0, read);
+        }
+    }
+
+    /* user gets back to the activity, e.g. through task manager */
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+    }
+
+    /* activity gets back to the foreground and user input */
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onBackPressed() {
+
+        if (mNavigationDrawer.isDrawerVisible(Gravity.LEFT)) {
+            mNavigationDrawer.closeDrawer(Gravity.LEFT);
+            return;
+        }
+
+        if (mContactDrawer.isExpanded() || mContactDrawer.isAnchored()) {
+            mContactDrawer.collapsePane();
+            return;
+        }
+
+        if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
+            popCustomBackStack();
+            fMenu.backToHome();
+            return;
+        }
+
+        if (isClosing) {
+            super.onBackPressed();
+            t.cancel();
+            finish();
+        } else {
+            t.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    isClosing = false;
+                }
+            }, 3000);
+            Toast.makeText(this, getResources().getString(R.string.close_msg), Toast.LENGTH_SHORT).show();
+            isClosing = true;
+        }
+    }
+
+    private void popCustomBackStack() {
+        FragmentManager fm = getSupportFragmentManager();
+        FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(0);
+        fContent = fm.findFragmentByTag(entry.getName());
+        for (int i = 0; i < fm.getBackStackEntryCount() - 1; ++i) {
+            fm.popBackStack();
+        }
+    }
+
+    /* activity no more in foreground */
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /* activity finishes itself or is being killed by the system */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Log.i(TAG, "onDestroy: destroying service...");
+        Intent sipServiceIntent = new Intent(this, SipService.class);
+        stopService(sipServiceIntent);
+    }
+
+    public void launchCallActivity(SipCall infos) {
+        Conference tmp = new Conference(Conference.DEFAULT_ID);
+
+        tmp.getParticipants().add(infos);
+        Intent intent = new Intent().setClass(this, CallActivity.class);
+        intent.putExtra("conference", tmp);
+        intent.putExtra("resuming", false);
+        startActivityForResult(intent, REQUEST_CODE_CALL);
+
+        // overridePendingTransition(R.anim.slide_down, R.anim.slide_up);
+    }
+
+    /**
+     * Defines callbacks for service binding, passed to bindService()
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            service = ISipService.Stub.asInterface(binder);
+            fMenu = new MenuFragment();
+            fContent = new HomeFragment();
+            getSupportFragmentManager().beginTransaction().replace(R.id.left_drawer, fMenu).replace(R.id.main_frame, fContent, "Home").addToBackStack("Home").commit();
+            mBound = true;
+            Log.d(TAG, "Service connected service=" + service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+
+            mBound = false;
+            Log.d(TAG, "Service disconnected service=" + service);
+        }
+    };
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Log.i(TAG, "onOptionsItemSelected " + item.getItemId());
+
+        if (mDrawerToggle.onOptionsItemSelected(item)) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        switch (requestCode) {
+            case REQUEST_CODE_PREFERENCES:
+            case AccountsManagementFragment.ACCOUNT_EDIT_REQUEST:
+                if (fMenu != null)
+                    fMenu.updateAllAccounts();
+                break;
+            case REQUEST_CODE_CALL:
+                if (resultCode == CallActivity.RESULT_FAILURE) {
+                    Log.w(TAG, "Call Failed");
+                }
+                break;
+        }
+
+    }
+
+    @Override
+    public ISipService getService() {
+        return service;
+    }
+
+    @Override
+    public void onTextContact(final CallContact c) {
+        // TODO
+    }
+
+    @Override
+    public void onEditContact(final CallContact c) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(c.getId()));
+        intent.setData(uri);
+        startActivity(intent);
+    }
+
+    @Override
+    public void onCallContact(final CallContact c) {
+
+        if (fMenu.getSelectedAccount() == null) {
+            createAccountDialog().show();
+            return;
+        }
+
+        if (!fMenu.getSelectedAccount().isRegistered()) {
+            createNotRegisteredDialog().show();
+            return;
+        }
+
+        getActionBar().show();
+        Thread launcher = new Thread(new Runnable() {
+
+            final String[] CONTACTS_PHONES_PROJECTION = new String[]{Phone.NUMBER, Phone.TYPE};
+            final String[] CONTACTS_SIP_PROJECTION = new String[]{SipAddress.SIP_ADDRESS, SipAddress.TYPE};
+
+            @Override
+            public void run() {
+
+                Bundle args = new Bundle();
+                args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt())));
+                args.putParcelable(SipCall.ACCOUNT, fMenu.getSelectedAccount());
+                args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE);
+                args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING);
+
+                Cursor cPhones = getContentResolver().query(Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION, Phone.CONTACT_ID + " =" + c.getId(),
+                        null, null);
+
+                while (cPhones.moveToNext()) {
+                    c.addPhoneNumber(cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER)), cPhones.getInt(cPhones.getColumnIndex(Phone.TYPE)));
+                }
+                cPhones.close();
+
+                Cursor cSip = getContentResolver().query(Phone.CONTENT_URI, CONTACTS_SIP_PROJECTION, Phone.CONTACT_ID + "=" + c.getId(), null,
+                        null);
+
+                while (cSip.moveToNext()) {
+                    c.addSipNumber(cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS)), cSip.getInt(cSip.getColumnIndex(SipAddress.TYPE)));
+                }
+                cSip.close();
+
+                args.putParcelable(SipCall.CONTACT, c);
+
+                launchCallActivity(new SipCall(args));
+            }
+        });
+        launcher.start();
+        mContactDrawer.collapsePane();
+
+    }
+
+    @Override
+    public void onCallHistory(HistoryEntry to) {
+
+        Account usedAccount = fMenu.retrieveAccountById(to.getAccountID());
+
+        if (usedAccount == null) {
+            createAccountDialog().show();
+            return;
+        }
+
+        if (usedAccount.isRegistered()) {
+            Bundle args = new Bundle();
+            args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt())));
+            args.putParcelable(SipCall.ACCOUNT, usedAccount);
+            args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE);
+            args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING);
+            args.putParcelable(SipCall.CONTACT, to.getContact());
+
+            try {
+                launchCallActivity(new SipCall(args));
+            } catch (Exception e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            createNotRegisteredDialog().show();
+        }
+    }
+
+    @Override
+    public void onCallDialed(String to) {
+        Account usedAccount = fMenu.getSelectedAccount();
+
+        if (usedAccount == null) {
+            createAccountDialog().show();
+            return;
+        }
+
+        if (fMenu.getSelectedAccount().isRegistered()) {
+            Bundle args = new Bundle();
+            args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt())));
+            args.putParcelable(SipCall.ACCOUNT, usedAccount);
+            args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE);
+            args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING);
+            args.putParcelable(SipCall.CONTACT, CallContact.ContactBuilder.buildUnknownContact(to));
+
+            try {
+                launchCallActivity(new SipCall(args));
+            } catch (Exception e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            createNotRegisteredDialog().show();
+        }
+    }
+
+    private AlertDialog createNotRegisteredDialog() {
+        final Activity ownerActivity = this;
+        AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+
+        builder.setMessage(getResources().getString(R.string.cannot_pass_sipcall))
+                .setTitle(getResources().getString(R.string.cannot_pass_sipcall_title))
+                .setPositiveButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+
+                    }
+                });
+
+        AlertDialog alertDialog = builder.create();
+        alertDialog.setOwnerActivity(ownerActivity);
+
+        return alertDialog;
+    }
+
+    private AlertDialog createAccountDialog() {
+        final Activity ownerActivity = this;
+        AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+
+        builder.setMessage(getResources().getString(R.string.create_new_account_dialog))
+                .setTitle(getResources().getString(R.string.create_new_account_dialog_title))
+                .setPositiveButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        Intent in = new Intent();
+                        in.setClass(ownerActivity, AccountWizard.class);
+                        ownerActivity.startActivityForResult(in, HomeActivity.REQUEST_CODE_PREFERENCES);
+                    }
+                }).setNegativeButton(getResources().getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                dialog.dismiss();
+            }
+        });
+
+        AlertDialog alertDialog = builder.create();
+        alertDialog.setOwnerActivity(ownerActivity);
+
+        return alertDialog;
+    }
+
+    @Override
+    public void onContactDragged() {
+        mContactDrawer.collapsePane();
+    }
+
+    @Override
+    public void toggleDrawer() {
+        if (mContactDrawer.isAnchored())
+            mContactDrawer.expandPane();
+        else if (!mContactDrawer.isExpanded())
+            mContactDrawer.expandPane(0.3f);
+        else
+            mContactDrawer.collapsePane();
+    }
+
+    @Override
+    public void toggleForSearchDrawer() {
+        if (mContactDrawer.isExpanded()) {
+            mContactDrawer.collapsePane();
+        } else
+            mContactDrawer.expandPane();
+    }
+
+    @Override
+    public void setDragView(RelativeLayout relativeLayout) {
+        mContactDrawer.setDragView(relativeLayout);
+    }
+
+    @Override
+    public void onSectionSelected(int pos) {
+
+        mNavigationDrawer.closeDrawers();
+
+        switch (pos) {
+            case 0:
+
+                if (fContent instanceof HomeFragment)
+                    break;
+
+                if (getSupportFragmentManager().getBackStackEntryCount() == 1)
+                    break;
+
+                popCustomBackStack();
+
+                break;
+            case 1:
+                if (fContent instanceof AccountsManagementFragment)
+                    break;
+                fContent = new AccountsManagementFragment();
+                getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).replace(R.id.main_frame, fContent, "Accounts").addToBackStack("Accounts").commit();
+                break;
+            case 2:
+                if (fContent instanceof AboutFragment)
+                    break;
+                fContent = new AboutFragment();
+                getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).replace(R.id.main_frame, fContent, "About").addToBackStack("About").commit();
+                break;
+        }
+
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/AboutFragment.java b/ring-android/src/cx/ring/fragments/AboutFragment.java
new file mode 100644
index 0000000..be1037c
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AboutFragment.java
@@ -0,0 +1,42 @@
+package cx.ring.fragments;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import cx.ring.R;
+
+public class AboutFragment extends Fragment {
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getActivity().getActionBar().setTitle(R.string.menu_item_about);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_about, parent, false);
+
+        TextView title = (TextView) inflatedView.findViewById(R.id.app_name);
+        try {
+            String versionName = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName;
+            title.setText(getString(R.string.app_name) + " - " + versionName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        TextView link = (TextView) inflatedView.findViewById(R.id.web_site);
+        String linkText = "<a href='http://sflphone.org/'>" + getResources().getString(R.string.web_site) + "</a>";
+        link.setText(Html.fromHtml(linkText));
+        link.setMovementMethod(LinkMovementMethod.getInstance());
+
+        return inflatedView;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/AccountCreationFragment.java b/ring-android/src/cx/ring/fragments/AccountCreationFragment.java
new file mode 100644
index 0000000..c6c272a
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AccountCreationFragment.java
@@ -0,0 +1,213 @@
+package cx.ring.fragments;
+
+import java.util.HashMap;
+
+import cx.ring.R;
+import cx.ring.model.account.AccountDetailBasic;
+import cx.ring.client.HomeActivity;
+import cx.ring.service.ISipService;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+import cx.ring.views.PasswordEditText;
+
+public class AccountCreationFragment extends Fragment {
+
+    // Values for email and password at the time of the login attempt.
+    private String mAlias;
+    private String mHostname;
+    private String mUsername;
+    private String mPassword;
+    private String mAccountType;
+
+    // UI references.
+    private EditText mAliasView;
+    private EditText mHostnameView;
+    private EditText mUsernameView;
+    private PasswordEditText mPasswordView;
+    private Spinner mAccountTypeView;
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+    };
+
+    public interface Callbacks {
+
+        public ISipService getService();
+
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_account_creation, parent, false);
+
+        mAliasView = (EditText) inflatedView.findViewById(R.id.alias);
+        mHostnameView = (EditText) inflatedView.findViewById(R.id.hostname);
+        mUsernameView = (EditText) inflatedView.findViewById(R.id.username);
+        mPasswordView = (PasswordEditText) inflatedView.findViewById(R.id.password);
+		mAccountTypeView = (Spinner) inflatedView.findViewById(R.id.account_type);
+
+        mPasswordView.getEdit_text().setOnEditorActionListener(new OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                // if(actionId == EditorInfo.IME_ACTION_GO || event.getAction() == KeyEvent.KEYCODE_ENTER){
+                mAlias = mAliasView.getText().toString();
+                mHostname = mHostnameView.getText().toString();
+                mUsername = mUsernameView.getText().toString();
+                mPassword = mPasswordView.getText().toString();
+				mAccountType = mAccountTypeView.getSelectedItem().toString();
+                attemptCreation();
+                // }
+
+                return true;
+            }
+        });
+        inflatedView.findViewById(R.id.create_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mAlias = mAliasView.getText().toString();
+                mHostname = mHostnameView.getText().toString();
+                mUsername = mUsernameView.getText().toString();
+                mPassword = mPasswordView.getText().toString();
+				mAccountType = mAccountTypeView.getSelectedItem().toString();
+                attemptCreation();
+            }
+        });
+
+        return inflatedView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    /**
+     * Attempts to sign in or register the account specified by the login form. If there are form errors (invalid email, missing fields, etc.), the
+     * errors are presented and no actual login attempt is made.
+     */
+    public void attemptCreation() {
+
+        // Reset errors.
+        mAliasView.setError(null);
+        mPasswordView.setError(null);
+
+        // Store values at the time of the login attempt.
+
+        boolean cancel = false;
+        View focusView = null;
+
+        // Check for a valid password.
+        if (TextUtils.isEmpty(mPassword)) {
+            mPasswordView.setError(getString(R.string.error_field_required));
+            focusView = mPasswordView;
+            cancel = true;
+        }
+
+        if (TextUtils.isEmpty(mUsername)) {
+            mUsernameView.setError(getString(R.string.error_field_required));
+            focusView = mUsernameView;
+            cancel = true;
+        }
+
+        if (TextUtils.isEmpty(mHostname)) {
+            mHostnameView.setError(getString(R.string.error_field_required));
+            focusView = mHostnameView;
+            cancel = true;
+        }
+
+        // Check for a valid email address.
+        if (TextUtils.isEmpty(mAlias)) {
+            mAliasView.setError(getString(R.string.error_field_required));
+            focusView = mAliasView;
+            cancel = true;
+        }
+
+        if (cancel) {
+            // There was an error; don't attempt login and focus the first
+            // form field with an error.
+            focusView.requestFocus();
+        } else {
+            // Show a progress spinner, and kick off a background task to
+            // perform the user login attempt.
+            initCreation();
+
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void initCreation() {
+
+        try {
+            HashMap<String, String> accountDetails = (HashMap<String, String>) mCallbacks.getService().getAccountTemplate();
+            accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS, mAlias);
+            accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME, mHostname);
+            accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME, mUsername);
+            accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD, mPassword);
+			accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_TYPE, mAccountType);
+
+            createNewAccount(accountDetails);
+
+        } catch (RemoteException e) {
+            Toast.makeText(getActivity(), "Error creating account", Toast.LENGTH_SHORT).show();
+            e.printStackTrace();
+        }
+
+        Intent resultIntent = new Intent(getActivity(), HomeActivity.class);
+        getActivity().setResult(Activity.RESULT_OK, resultIntent);
+        resultIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        startActivity(resultIntent);
+        getActivity().finish();
+
+    }
+
+    private void createNewAccount(HashMap<String, String> accountDetails) {
+        try {
+
+            mCallbacks.getService().addAccount(accountDetails);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/AccountWrapperFragment.java b/ring-android/src/cx/ring/fragments/AccountWrapperFragment.java
new file mode 100644
index 0000000..6172708
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AccountWrapperFragment.java
@@ -0,0 +1,101 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import cx.ring.interfaces.AccountsInterface;
+import cx.ring.service.ConfigurationManagerCallback;
+
+public abstract class AccountWrapperFragment extends Fragment implements AccountsInterface {
+
+
+    private AccountsReceiver mReceiver;
+
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mReceiver = new AccountsReceiver();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED);
+        intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_CHANGED);
+        getActivity().registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    public void accountsChanged() {
+
+    }
+
+    @Override
+    public void accountStateChanged(String accoundID, String state, int code) {
+
+    }
+
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        getActivity().unregisterReceiver(mReceiver);
+    }
+
+    public class AccountsReceiver extends BroadcastReceiver {
+
+        private final String TAG = AccountsReceiver.class.getSimpleName();
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().contentEquals(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED)) {
+                Log.i(TAG, "Received" + intent.getAction());
+                accountStateChanged(intent.getStringExtra("Account"), intent.getStringExtra("state"), intent.getIntExtra("code", 0));
+            } else if (intent.getAction().contentEquals(ConfigurationManagerCallback.ACCOUNTS_CHANGED)) {
+                Log.i(TAG, "Received" + intent.getAction());
+                accountsChanged();
+
+            }
+
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/fragments/AccountsManagementFragment.java b/ring-android/src/cx/ring/fragments/AccountsManagementFragment.java
new file mode 100644
index 0000000..09da403
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AccountsManagementFragment.java
@@ -0,0 +1,419 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *      Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.*;
+import android.view.View.OnClickListener;
+import android.widget.*;
+import android.widget.AdapterView.OnItemClickListener;
+import cx.ring.R;
+import cx.ring.client.AccountEditionActivity;
+import cx.ring.client.AccountWizard;
+import cx.ring.loaders.AccountsLoader;
+import cx.ring.loaders.LoaderConstants;
+import cx.ring.model.account.Account;
+import cx.ring.service.ISipService;
+import cx.ring.views.dragsortlv.DragSortListView;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class AccountsManagementFragment extends AccountWrapperFragment implements LoaderManager.LoaderCallbacks<Bundle> {
+    static final String TAG = "AccountManagementFragment";
+    static final String DEFAULT_ACCOUNT_ID = "IP2IP";
+    static final int ACCOUNT_CREATE_REQUEST = 1;
+    public static final int ACCOUNT_EDIT_REQUEST = 2;
+    AccountsAdapter mAccountsAdapter;
+    AccountsAdapter mIP2IPAdapter;
+
+    DragSortListView mDnDListView;
+    private View mLoadingView;
+    private int mShortAnimationDuration;
+
+    private DragSortListView.DropListener onDrop = new DragSortListView.DropListener() {
+        @Override
+        public void drop(int from, int to) {
+            if (from != to) {
+                Account item = mAccountsAdapter.getItem(from);
+                mAccountsAdapter.remove(item);
+                mAccountsAdapter.insert(item, to);
+                try {
+                    mCallbacks.getService().setAccountOrder(mAccountsAdapter.generateAccountOrder());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+    };
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private Account ip2ip;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+    };
+    private AccountsLoader accountsLoader;
+
+    public interface Callbacks {
+
+        public ISipService getService();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.i(TAG, "Create Account Management Fragment");
+        mAccountsAdapter = new AccountsAdapter(getActivity(), new ArrayList<Account>());
+        mIP2IPAdapter = new AccountsAdapter(getActivity(), new ArrayList<Account>());
+        this.setHasOptionsMenu(true);
+
+        mShortAnimationDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
+        Log.i(TAG, "anim time: " + mShortAnimationDuration);
+        getLoaderManager().initLoader(LoaderConstants.ACCOUNTS_LOADER, null, this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_accounts_list, parent, false);
+        ((ListView)inflatedView.findViewById(R.id.accounts_list)).setAdapter(mAccountsAdapter);
+
+        return inflatedView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mDnDListView = (DragSortListView) getView().findViewById(R.id.accounts_list);
+
+        mDnDListView.setDropListener(onDrop);
+        mDnDListView.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+                launchAccountEditActivity(mAccountsAdapter.getItem(pos));
+            }
+        });
+
+        ((ListView) getView().findViewById(R.id.ip2ip)).setAdapter(mIP2IPAdapter);
+        ((ListView) getView().findViewById(R.id.ip2ip)).setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
+                launchAccountEditActivity(ip2ip);
+
+            }
+        });
+
+        mLoadingView = view.findViewById(R.id.loading_spinner);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    public void onResume() {
+        super.onResume();
+        accountsLoader.onContentChanged();
+        getActivity().getActionBar().setTitle(R.string.menu_item_accounts);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu m, MenuInflater inf) {
+        super.onCreateOptionsMenu(m, inf);
+        inf.inflate(R.menu.account_creation, m);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (item.getItemId()) {
+            case R.id.menuitem_create:
+                Intent intent = new Intent().setClass(getActivity(), AccountWizard.class);
+                startActivityForResult(intent, ACCOUNT_CREATE_REQUEST);
+                break;
+        }
+
+        return true;
+    }
+
+    private void launchAccountEditActivity(Account acc) {
+        Log.i(TAG, "Launch account edit activity");
+
+        Intent intent = new Intent().setClass(getActivity(), AccountEditionActivity.class);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable("account", acc);
+
+        intent.putExtras(bundle);
+
+        startActivityForResult(intent, ACCOUNT_EDIT_REQUEST);
+    }
+
+    @Override
+    public void accountsChanged() {
+        accountsLoader.onContentChanged();
+    }
+
+    @Override
+    public void accountStateChanged(String accoundID, String state, int code) {
+        mAccountsAdapter.updateAccount(accoundID, state, code);
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        accountsLoader.onContentChanged();
+    }
+
+    /**
+     * Adapter for accounts List
+     *
+     * @author lisional
+     */
+    public class AccountsAdapter extends BaseAdapter {
+
+        // private static final String TAG = AccountSelectionAdapter.class.getSimpleName();
+
+        ArrayList<Account> accounts;
+        Context mContext;
+
+        public AccountsAdapter(Context cont, ArrayList<Account> newList) {
+            super();
+            accounts = newList;
+            mContext = cont;
+        }
+
+        public void insert(Account item, int to) {
+            accounts.add(to, item);
+            notifyDataSetChanged();
+        }
+
+        public void remove(Account item) {
+            accounts.remove(item);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        @Override
+        public int getCount() {
+            return accounts.size();
+        }
+
+        @Override
+        public Account getItem(int pos) {
+            return accounts.get(pos);
+        }
+
+        @Override
+        public long getItemId(int pos) {
+            return 0;
+        }
+
+        @Override
+        public View getView(final int pos, View convertView, ViewGroup parent) {
+            View rowView = convertView;
+            AccountView entryView;
+
+            if (rowView == null) {
+                LayoutInflater inflater = LayoutInflater.from(mContext);
+                rowView = inflater.inflate(R.layout.item_account_pref, null);
+
+                entryView = new AccountView();
+                entryView.alias = (TextView) rowView.findViewById(R.id.account_alias);
+                entryView.host = (TextView) rowView.findViewById(R.id.account_host);
+                entryView.enabled = (CheckBox) rowView.findViewById(R.id.account_checked);
+                rowView.setTag(entryView);
+            } else {
+                entryView = (AccountView) rowView.getTag();
+            }
+
+            final Account item = accounts.get(pos);
+            entryView.alias.setText(accounts.get(pos).getAlias());
+            if (item.isIP2IP()) {
+                entryView.host.setText(item.getRegistered_state());
+                entryView.enabled.setVisibility(View.GONE);
+            } else {
+                entryView.host.setText(item.getHost() + " - " + item.getRegistered_state());
+                entryView.enabled.setChecked(item.isEnabled());
+                entryView.enabled.setOnClickListener(new OnClickListener() {
+
+                    @Override
+                    public void onClick(View v) {
+                        item.setEnabled(!item.isEnabled());
+
+                        try {
+                            mCallbacks.getService().setAccountDetails(item.getAccountID(), item.getDetails());
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+
+            return rowView;
+        }
+
+        /**
+         * ******************
+         * ViewHolder Pattern
+         * *******************
+         */
+        public class AccountView {
+            public TextView alias;
+            public TextView host;
+            public CheckBox enabled;
+        }
+
+        public void removeAll() {
+            accounts.clear();
+            notifyDataSetChanged();
+
+        }
+
+        public void addAll(ArrayList<Account> results) {
+            accounts.addAll(results);
+            notifyDataSetChanged();
+        }
+
+        /**
+         * Modify state of specific account
+         */
+        public void updateAccount(String accoundID, String state, int code) {
+            Log.i(TAG, "updateAccount:" + state);
+            for (Account a : accounts) {
+                if (a.getAccountID().contentEquals(accoundID)) {
+                    a.setRegistered_state(state);
+                    notifyDataSetChanged();
+                    return;
+                }
+            }
+
+        }
+
+        private String generateAccountOrder() {
+            String result = DEFAULT_ACCOUNT_ID + File.separator;
+            for (Account a : accounts) {
+                result += a.getAccountID() + File.separator;
+            }
+            return result;
+        }
+
+    }
+
+    private void crossfade() {
+
+        // Set the content view to 0% opacity but visible, so that it is visible
+        // (but fully transparent) during the animation.
+        mDnDListView.setAlpha(0f);
+        mDnDListView.setVisibility(View.VISIBLE);
+
+        // Animate the content view to 100% opacity, and clear any animation
+        // listener set on the view.
+        mDnDListView.animate().alpha(1f).setDuration(mShortAnimationDuration).setListener(null);
+
+        // Animate the loading view to 0% opacity. After the animation ends,
+        // set its visibility to GONE as an optimization step (it won't
+        // participate in layout passes, etc.)
+        mLoadingView.animate().alpha(0f).setDuration(mShortAnimationDuration).setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mLoadingView.setVisibility(View.GONE);
+            }
+        });
+    }
+
+
+    @Override
+    public AsyncTaskLoader<Bundle> onCreateLoader(int arg0, Bundle arg1) {
+        accountsLoader = new AccountsLoader(getActivity(), mCallbacks.getService());
+        return accountsLoader;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Bundle> bundleLoader, Bundle results) {
+        mAccountsAdapter.removeAll();
+        ArrayList<Account> tmp = results.getParcelableArrayList(AccountsLoader.ACCOUNTS);
+        ip2ip = results.getParcelable(AccountsLoader.ACCOUNT_IP2IP);
+        mAccountsAdapter.addAll(tmp);
+        mIP2IPAdapter.removeAll();
+        mIP2IPAdapter.insert(ip2ip, 0);
+        if (mAccountsAdapter.isEmpty()) {
+            mDnDListView.setEmptyView(getView().findViewById(R.id.empty_account_list));
+        }
+        crossfade();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Bundle> bundleLoader) {
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/AdvancedAccountFragment.java b/ring-android/src/cx/ring/fragments/AdvancedAccountFragment.java
new file mode 100644
index 0000000..71e7da4
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AdvancedAccountFragment.java
@@ -0,0 +1,194 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import cx.ring.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+import cx.ring.model.account.AccountDetail;
+import cx.ring.model.account.AccountDetailAdvanced;
+import cx.ring.model.account.Account;
+
+public class AdvancedAccountFragment extends PreferenceFragment {
+
+    private static final String TAG = AdvancedAccountFragment.class.getSimpleName();
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public Account getAccount() {
+            return null;
+        }
+
+    };
+
+    public interface Callbacks {
+
+        public Account getAccount();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.account_advanced_prefs);
+        setPreferenceDetails(mCallbacks.getAccount().getAdvancedDetails());
+        addPreferenceListener(mCallbacks.getAccount().getAdvancedDetails(), changeAdvancedPreferenceListener);
+
+    }
+
+    private void setPreferenceDetails(AccountDetail details) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "setPreferenceDetails: pref " + p.mKey + " value " + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+                if (p.mKey == AccountDetailAdvanced.CONFIG_LOCAL_INTERFACE) {
+                    ArrayList<CharSequence> entries = getNetworkInterfaces();
+                    CharSequence[] display = new CharSequence[entries.size()];
+                    entries.toArray(display);
+                    ((ListPreference) pref).setEntries(display);
+                    ((ListPreference) pref).setEntryValues(display);
+                    pref.setSummary(p.mValue);
+                    continue;
+                }
+                if (!p.isTwoState) {
+                    pref.setSummary(p.mValue);
+                } else if (pref.getKey().contentEquals(AccountDetailAdvanced.CONFIG_STUN_ENABLE)) {
+                    ((CheckBoxPreference) pref).setChecked(p.mValue.contentEquals("true"));
+                    findPreference(AccountDetailAdvanced.CONFIG_STUN_SERVER).setEnabled(p.mValue.contentEquals("true"));
+                } else if (pref.getKey().contentEquals("Account.publishedSameAsLocal")) {
+                    ((CheckBoxPreference) pref).setChecked(p.mValue.contentEquals("true"));
+                    findPreference(AccountDetailAdvanced.CONFIG_PUBLISHED_PORT).setEnabled(!p.mValue.contentEquals("true"));
+                    findPreference(AccountDetailAdvanced.CONFIG_PUBLISHED_ADDRESS).setEnabled(!p.mValue.contentEquals("true"));
+                }
+            } else {
+                Log.w(TAG, "pref not found");
+            }
+        }
+    }
+
+    private ArrayList<CharSequence> getNetworkInterfaces() {
+        ArrayList<CharSequence> result = new ArrayList<CharSequence>();
+        result.add("default");
+        try {
+
+            for (Enumeration<NetworkInterface> list = NetworkInterface.getNetworkInterfaces(); list.hasMoreElements();) {
+                NetworkInterface i = list.nextElement();
+                if (i.isUp())
+                    result.add(i.getDisplayName());
+            }
+        } catch (SocketException e) {
+            Log.e(TAG, e.toString());
+        }
+        return result;
+    }
+
+    private void addPreferenceListener(AccountDetail details, OnPreferenceChangeListener listener) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "addPreferenceListener: pref " + p.mKey + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+
+                pref.setOnPreferenceChangeListener(listener);
+
+            } else {
+                Log.w(TAG, "addPreferenceListener: pref not found");
+            }
+        }
+    }
+
+    Preference.OnPreferenceChangeListener changeAdvancedPreferenceListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+
+            if (preference instanceof CheckBoxPreference) {
+                mCallbacks.getAccount().getAdvancedDetails().setDetailString(preference.getKey(), newValue.toString());
+                if (preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_STUN_ENABLE)) {
+                    findPreference(AccountDetailAdvanced.CONFIG_STUN_SERVER).setEnabled((Boolean) newValue);
+                } else if (preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_PUBLISHED_SAMEAS_LOCAL)) {
+                    findPreference(AccountDetailAdvanced.CONFIG_PUBLISHED_PORT).setEnabled(!(Boolean) newValue);
+                    findPreference(AccountDetailAdvanced.CONFIG_PUBLISHED_ADDRESS).setEnabled(!(Boolean) newValue);
+                }
+            } else {
+                Log.i(TAG, "Changing" + preference.getKey() + " value:" + newValue);
+                if(preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MAX) ||
+                        preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MIN))
+                    newValue = adjustRtpRange(Integer.valueOf((String) newValue));
+
+                preference.setSummary((CharSequence) newValue);
+                mCallbacks.getAccount().getAdvancedDetails().setDetailString(preference.getKey(), newValue.toString());
+            }
+
+            mCallbacks.getAccount().notifyObservers();
+            return true;
+        }
+    };
+
+    private String adjustRtpRange(int newValue) {
+        if(newValue < 1024)
+            return "1024";
+        if(newValue > 65534)
+            return "65534";
+        return String.valueOf(newValue);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/AudioManagementFragment.java b/ring-android/src/cx/ring/fragments/AudioManagementFragment.java
new file mode 100644
index 0000000..71789e4
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/AudioManagementFragment.java
@@ -0,0 +1,435 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *          Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import android.content.Intent;
+import cx.ring.R;
+import cx.ring.model.account.AccountDetail;
+import cx.ring.model.account.AccountDetailAdvanced;
+import cx.ring.model.account.Account;
+import cx.ring.model.Codec;
+import cx.ring.service.ISipService;
+import cx.ring.views.dragsortlv.DragSortListView;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class AudioManagementFragment extends PreferenceFragment {
+    static final String TAG = AudioManagementFragment.class.getSimpleName();
+
+    protected Callbacks mCallbacks = sDummyCallbacks;
+    ArrayList<Codec> codecs;
+    private DragSortListView mCodecList;
+    CodecAdapter listAdapter;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+        @Override
+        public Account getAccount() {
+            return null;
+        }
+
+    };
+
+    public interface Callbacks {
+
+        public ISipService getService();
+
+        public Account getAccount();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+        try {
+            codecs = (ArrayList<Codec>) mCallbacks.getService().getAudioCodecList(mCallbacks.getAccount().getAccountID());
+            mCallbacks.getService().getRingtoneList();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    private DragSortListView.DropListener onDrop = new DragSortListView.DropListener() {
+        @Override
+        public void drop(int from, int to) {
+            if (from != to) {
+                Codec item = listAdapter.getItem(from);
+                listAdapter.remove(item);
+                listAdapter.insert(item, to);
+                try {
+                    mCallbacks.getService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    };
+
+    private ListView mPrefsList;
+
+    public ArrayList<String> getActiveCodecList() {
+        ArrayList<String> results = new ArrayList<String>();
+        for (int i = 0; i < listAdapter.getCount(); ++i) {
+            if (listAdapter.getItem(i).isEnabled()) {
+                results.add(listAdapter.getItem(i).getPayload().toString());
+            }
+        }
+        return results;
+    }
+
+    private static final int SELECT_RINGTONE_PATH = 40;
+    private Preference.OnPreferenceClickListener filePickerListener = new Preference.OnPreferenceClickListener() {
+        @Override
+        public boolean onPreferenceClick(Preference preference) {
+            performFileSearch(SELECT_RINGTONE_PATH);
+            return true;
+        }
+    };
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == Activity.RESULT_CANCELED)
+            return;
+
+        File myFile = new File(data.getData().getPath());
+        Log.i(TAG, "file selected:" + data.getData());
+        if (requestCode == SELECT_RINGTONE_PATH) {
+            findPreference(AccountDetailAdvanced.CONFIG_RINGTONE_PATH).setSummary(myFile.getName());
+            mCallbacks.getAccount().getAdvancedDetails().setDetailString(AccountDetailAdvanced.CONFIG_RINGTONE_PATH, myFile.getAbsolutePath());
+            mCallbacks.getAccount().notifyObservers();
+        }
+
+    }
+
+    public void performFileSearch(int requestCodeToSet) {
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("audio/*");
+        startActivityForResult(intent, requestCodeToSet);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.frag_audio_mgmt, null);
+
+        mPrefsList = (ListView) rootView.findViewById(android.R.id.list);
+        mCodecList = (DragSortListView) rootView.findViewById(R.id.dndlistview);
+        mCodecList.setAdapter(listAdapter);
+        mCodecList.setDropListener(onDrop);
+        mCodecList.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+                listAdapter.getItem(pos).toggleState();
+                listAdapter.notifyDataSetChanged();
+                try {
+                    mCallbacks.getService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+
+            }
+        });
+        return rootView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        final LinearLayout holder = (LinearLayout) getView().findViewById(R.id.lv_holder);
+        final LinearLayout holder_prefs = (LinearLayout) getView().findViewById(R.id.lv_holder_prefs);
+        holder.post(new Runnable() {
+
+            @Override
+            public void run() {
+                setListViewHeight(mCodecList, holder);
+                setListViewHeight(mPrefsList, holder_prefs);
+            }
+        });
+
+    }
+
+    // Sets the ListView holder's height
+    public void setListViewHeight(ListView listView, LinearLayout llMain) {
+        ListAdapter listAdapter = listView.getAdapter();
+        if (listAdapter == null) {
+            return;
+        }
+
+        int totalHeight = 0;
+        int firstHeight;
+        int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
+
+        for (int i = 0; i < listAdapter.getCount(); i++) {
+            View listItem = listAdapter.getView(i, null, listView);
+            listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
+            firstHeight = listItem.getMeasuredHeight();
+            totalHeight += firstHeight;
+        }
+
+        totalHeight += getView().findViewById(R.id.list_header_title).getMeasuredHeight();
+        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) llMain.getLayoutParams();
+        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount()));
+        llMain.setLayoutParams(params);
+        getView().requestLayout();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.account_audio_prefs);
+        listAdapter = new CodecAdapter(getActivity());
+        listAdapter.setDataset(codecs);
+
+        setPreferenceDetails(mCallbacks.getAccount().getAdvancedDetails());
+        findPreference(AccountDetailAdvanced.CONFIG_RINGTONE_PATH).setEnabled(
+                ((CheckBoxPreference) findPreference(AccountDetailAdvanced.CONFIG_RINGTONE_ENABLED)).isChecked());
+        addPreferenceListener(mCallbacks.getAccount().getAdvancedDetails(), changeAudioPreferenceListener);
+    }
+
+    Preference.OnPreferenceChangeListener changeAudioPreferenceListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            if (preference instanceof CheckBoxPreference) {
+                if (preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_RINGTONE_ENABLED))
+                    getPreferenceScreen().findPreference(AccountDetailAdvanced.CONFIG_RINGTONE_PATH).setEnabled((Boolean) newValue);
+                mCallbacks.getAccount().getAdvancedDetails().setDetailString(preference.getKey(), newValue.toString());
+            } else {
+                if (preference.getKey().contentEquals(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE)) {
+                    preference.setSummary(((String)newValue).contentEquals("overrtp") ? "RTP" : "SIP");
+                } else {
+                    preference.setSummary((CharSequence) newValue);
+                    Log.i(TAG, "Changing" + preference.getKey() + " value:" + newValue);
+                    mCallbacks.getAccount().getAdvancedDetails().setDetailString(preference.getKey(), newValue.toString());
+                }
+            }
+            mCallbacks.getAccount().notifyObservers();
+
+            return true;
+        }
+    };
+
+    private void setPreferenceDetails(AccountDetail details) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "setPreferenceDetails: pref " + p.mKey + " value " + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+                if (!p.isTwoState) {
+                    if (p.mKey.contentEquals(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE)) {
+                        pref.setDefaultValue(p.mValue.contentEquals("overrtp") ? "RTP" : "SIP");
+                        pref.setSummary(p.mValue.contentEquals("overrtp") ? "RTP" : "SIP");
+                    } else {
+                        if(pref.getKey().contentEquals(AccountDetailAdvanced.CONFIG_RINGTONE_PATH)){
+                            File tmp = new File(p.mValue);
+                            pref.setSummary(tmp.getName());
+                        } else
+                            pref.setSummary(p.mValue);
+                    }
+
+                } else {
+                    ((CheckBoxPreference) pref).setChecked(p.mValue.contentEquals("true"));
+                }
+
+            } else {
+                Log.w(TAG, "pref not found");
+            }
+        }
+    }
+
+    private void addPreferenceListener(AccountDetail details, OnPreferenceChangeListener listener) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "addPreferenceListener: pref " + p.mKey + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener(listener);
+                if (pref.getKey().contentEquals(AccountDetailAdvanced.CONFIG_RINGTONE_PATH))
+                    pref.setOnPreferenceClickListener(filePickerListener);
+            } else {
+                Log.w(TAG, "addPreferenceListener: pref not found");
+            }
+        }
+    }
+
+    public static class CodecAdapter extends BaseAdapter {
+
+        ArrayList<Codec> items;
+        private Context mContext;
+
+        public CodecAdapter(Context context) {
+            items = new ArrayList<Codec>();
+            mContext = context;
+        }
+
+        public void insert(Codec item, int to) {
+            items.add(to, item);
+            notifyDataSetChanged();
+        }
+
+        public void remove(Codec item) {
+            items.remove(item);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return items.size();
+        }
+
+        @Override
+        public Codec getItem(int position) {
+            return items.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int pos, View convertView, ViewGroup parent) {
+            View rowView = convertView;
+            CodecView entryView;
+
+            if (rowView == null) {
+                LayoutInflater inflater = LayoutInflater.from(mContext);
+                rowView = inflater.inflate(R.layout.item_codec, null);
+
+                entryView = new CodecView();
+                entryView.name = (TextView) rowView.findViewById(R.id.codec_name);
+                entryView.samplerate = (TextView) rowView.findViewById(R.id.codec_samplerate);
+                entryView.enabled = (CheckBox) rowView.findViewById(R.id.codec_checked);
+                rowView.setTag(entryView);
+            } else {
+                entryView = (CodecView) rowView.getTag();
+            }
+
+            if (items.get(pos).isSpeex())
+                entryView.samplerate.setVisibility(View.VISIBLE);
+            else
+                entryView.samplerate.setVisibility(View.GONE);
+
+            entryView.name.setText(items.get(pos).getName());
+            entryView.samplerate.setText(items.get(pos).getSampleRate());
+            entryView.enabled.setChecked(items.get(pos).isEnabled());
+
+            return rowView;
+
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return true;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return true;
+        }
+
+        public void setDataset(ArrayList<Codec> codecs) {
+            items = new ArrayList<Codec>(codecs);
+        }
+
+        /**
+         * ******************
+         * ViewHolder Pattern
+         * *******************
+         */
+        public class CodecView {
+            public TextView name;
+            public TextView samplerate;
+            public CheckBox enabled;
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/fragments/CallFragment.java b/ring-android/src/cx/ring/fragments/CallFragment.java
new file mode 100644
index 0000000..b2b2406
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/CallFragment.java
@@ -0,0 +1,772 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PointF;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.support.v4.app.FragmentManager;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.*;
+import android.view.SurfaceHolder.Callback;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.*;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import cx.ring.R;
+import cx.ring.interfaces.CallInterface;
+import cx.ring.service.ISipService;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+import cx.ring.model.Attractor;
+import cx.ring.model.Bubble;
+import cx.ring.model.BubbleContact;
+import cx.ring.model.BubbleModel;
+import cx.ring.model.BubbleUser;
+import cx.ring.model.BubblesView;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conference;
+import cx.ring.model.SecureSipCall;
+import cx.ring.model.SipCall;
+
+public class CallFragment extends CallableWrapperFragment implements CallInterface, Callback {
+
+    static final String TAG = "CallFragment";
+
+
+
+    private float bubbleSize = 75; // dip
+    private float attractorSize = 40;
+    public static final int REQUEST_TRANSFER = 10;
+
+    // Screen wake lock for incoming call
+    private WakeLock mScreenWakeLock;
+
+    private BubblesView mBubbleView;
+    private BubbleModel mBubbleModel;
+
+    private Bitmap buttonCall;
+    private Bitmap buttonMsg;
+    private Bitmap buttonHold;
+    private Bitmap buttonUnhold;
+    private Bitmap buttonTransfer;
+    private Bitmap buttonHangUp;
+
+    private final int BTN_MSG_IDX = 0;
+    private final int BTN_HOLD_IDX = 1;
+    private final int BTN_TRANSFER_IDX = 2;
+    private final int BTN_HUNGUP_IDX = 3;
+
+    private BubbleModel.ActionGroup userActions;
+    private BubbleModel.ActionGroup callActions;
+
+    ViewSwitcher mSecuritySwitch;
+    private TextView mCallStatusTxt;
+    private ToggleButton mToggleSpeakers;
+
+    public Callbacks mCallbacks = sDummyCallbacks;
+    boolean accepted = false;
+
+    TransferDFragment editName;
+    private WifiManager wifiManager;
+    private BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            WifiInfo info = wifiManager.getConnectionInfo();
+            Log.i(TAG, "Level of wifi " + info.getRssi());
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedBundle) {
+        super.onCreate(savedBundle);
+
+        Resources r = getResources();
+
+        bubbleSize = r.getDimension(R.dimen.bubble_size);
+        attractorSize = r.getDimension(R.dimen.bubble_action_size);
+        float attractorMargin = r.getDimension(R.dimen.bubble_action_margin);
+
+        buttonCall = BitmapFactory.decodeResource(r, R.drawable.ic_action_call);
+        buttonMsg = BitmapFactory.decodeResource(r, R.drawable.ic_action_chat);
+        buttonHold = BitmapFactory.decodeResource(r, R.drawable.ic_action_pause_over_video);
+        buttonUnhold = BitmapFactory.decodeResource(r, R.drawable.ic_action_play_over_video);
+        buttonTransfer = BitmapFactory.decodeResource(r, R.drawable.ic_action_forward);
+        buttonHangUp = BitmapFactory.decodeResource(r, R.drawable.ic_action_end_call);
+
+        BubbleModel.ActionGroupCallback cb = new BubbleModel.ActionGroupCallback() {
+            @Override
+            public boolean onBubbleAction(Bubble b, int action) {
+                Log.i(TAG, "onBubbleAction ! "+action);
+                switch(action) {
+                    case BTN_HUNGUP_IDX:
+                        try {
+                            if (b.isConference())
+                                mCallbacks.getService().hangUpConference(b.getCallID());
+                            else
+                                mCallbacks.getService().hangUp(b.getCallID());
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                        return true;
+                    case BTN_HOLD_IDX:
+                        try {
+                            if (b.getHoldStatus()) {
+                                if (b.isConference())
+                                    mCallbacks.getService().unholdConference(b.getCallID());
+                                else
+                                    mCallbacks.getService().unhold(b.getCallID());
+                            } else {
+                                if (b.isConference())
+                                    mCallbacks.getService().holdConference(b.getCallID());
+                                else
+                                    mCallbacks.getService().hold(b.getCallID());
+                            }
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                        return false;
+                    case BTN_TRANSFER_IDX:
+                        makeTransfer((BubbleContact) b);
+                        return false;
+                }
+                return false;
+            }
+        };
+
+        userActions = new BubbleModel.ActionGroup(cb, attractorMargin, .4f, .25f);
+        userActions.addAction(BTN_HOLD_IDX, buttonHold, getString(R.string.action_call_hold), attractorSize);
+        userActions.addAction(BTN_HUNGUP_IDX, buttonHangUp, getString(R.string.action_call_hangup), attractorSize);
+
+        callActions = new BubbleModel.ActionGroup(cb, attractorMargin, .4f, .25f);
+        callActions.addAction(BTN_HOLD_IDX, buttonHold, getString(R.string.action_call_hold), attractorSize);
+        callActions.addAction(BTN_TRANSFER_IDX, buttonTransfer, getString(R.string.action_call_attended_transfer), attractorSize);
+        callActions.addAction(BTN_HUNGUP_IDX, buttonHangUp, getString(R.string.action_call_hangup), attractorSize);
+
+        mBubbleModel = new BubbleModel(r.getDisplayMetrics().density, new BubbleModel.ModelCallback() {
+            @Override
+            public void bubbleGrabbed(Bubble b) {
+                if (mBubbleModel.curState != BubbleModel.State.Incall) {
+                    return;
+                }
+                if (b.isUser) {
+                    mBubbleModel.setActions(b, userActions);
+                } else {
+                    mBubbleModel.setActions(b, callActions);
+                }
+            }
+
+            @Override
+            public boolean bubbleEjected(Bubble b) {
+                //if (b.isUser) {
+                try {
+                    if (b.isConference())
+                        mCallbacks.getService().hangUpConference(b.getCallID());
+                    else
+                        mCallbacks.getService().hangUp(b.getCallID());
+
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                return true;
+                /*}
+                return false;*/
+            }
+        });
+
+        setHasOptionsMenu(true);
+        PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
+        mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE,
+                "org.sflphone.onIncomingCall");
+        mScreenWakeLock.setReferenceCounted(false);
+
+        Log.d(TAG, "Acquire wake up lock");
+        if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
+            mScreenWakeLock.acquire();
+        }
+
+        mCallbacks.onFragmentCreated();
+    }
+
+    private void initializeWiFiListener() {
+        String connectivity_context = Context.WIFI_SERVICE;
+        wifiManager = (WifiManager) getActivity().getSystemService(connectivity_context);
+        getActivity().registerReceiver(wifiReceiver, new IntentFilter(WifiManager.RSSI_CHANGED_ACTION));
+    }
+
+    /**
+     * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
+     */
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public void onFragmentCreated() {
+
+        }
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+        @Override
+        public void terminateCall() {
+        }
+
+        @Override
+        public Conference getDisplayedConference() {
+            return null;
+        }
+
+        @Override
+        public void updateDisplayedConference(Conference c) {
+        }
+
+        @Override
+        public void startTimer() {
+        }
+
+        @Override
+        public void slideChatScreen() {
+        }
+
+    };
+
+    /**
+     * The Activity calling this fragment has to implement this interface
+     */
+    public interface Callbacks {
+
+        public void onFragmentCreated();
+
+        public ISipService getService();
+
+        public void startTimer();
+
+        public void slideChatScreen();
+
+        public void terminateCall();
+
+        public Conference getDisplayedConference();
+
+        public void updateDisplayedConference(Conference c);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        // rootView.requestDisallowInterceptTouchEvent(true);
+
+        mCallbacks = (Callbacks) activity;
+        // myself = SipCall.SipCallBuilder.buildMyselfCall(activity.getContentResolver(), "Me");
+
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu m, MenuInflater inf) {
+        super.onCreateOptionsMenu(m, inf);
+        inf.inflate(R.menu.ac_call, m);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (item.getItemId()) {
+            case R.id.menuitem_chat:
+                mCallbacks.slideChatScreen();
+                break;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        initializeWiFiListener();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        getActivity().unregisterReceiver(wifiReceiver);
+        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
+            mScreenWakeLock.release();
+        }
+    }
+
+    @Override
+    public void callStateChanged(Conference updated, String callID, String newState) {
+        mCallbacks.updateDisplayedConference(updated);
+        Log.i(TAG, "Call :" + callID + " " + newState);
+
+        if (getConference().isOnGoing()) {
+            initNormalStateDisplay();
+        } else if (getConference().isRinging()) {
+            mCallStatusTxt.setText(newState);
+
+            if (getConference().isIncoming()) {
+                initIncomingCallDisplay();
+            } else
+                initOutGoingCallDisplay();
+        } else {
+            mCallStatusTxt.setText(newState);
+            mCallbacks.terminateCall();
+        }
+    }
+
+    @Override
+    public void secureZrtpOn(Conference updated, String id) {
+        Log.i(TAG, "secureZrtpOn");
+        mCallbacks.updateDisplayedConference(updated);
+        updateSecurityDisplay();
+    }
+
+    @Override
+    public void secureZrtpOff(Conference updated, String id) {
+        Log.i(TAG, "secureZrtpOff");
+        mCallbacks.updateDisplayedConference(updated);
+        updateSecurityDisplay();
+    }
+
+    @Override
+    public void displaySAS(Conference updated, final String securedCallID) {
+        Log.i(TAG, "displaySAS");
+        mCallbacks.updateDisplayedConference(updated);
+        updateSecurityDisplay();
+    }
+
+    @Override
+    public void zrtpNegotiationFailed(Conference c, String securedCallID) {
+        mCallbacks.updateDisplayedConference(c);
+        updateSecurityDisplay();
+    }
+
+    @Override
+    public void zrtpNotSupported(Conference c, String securedCallID) {
+        mCallbacks.updateDisplayedConference(c);
+        updateSecurityDisplay();
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        SipCall transfer;
+        if (requestCode == REQUEST_TRANSFER) {
+            switch (resultCode) {
+                case TransferDFragment.RESULT_TRANSFER_CONF:
+                    Conference c = data.getParcelableExtra("target");
+                    transfer = data.getParcelableExtra("transfer");
+                    try {
+
+                        mCallbacks.getService().attendedTransfer(transfer.getCallId(), c.getParticipants().get(0).getCallId());
+
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+
+                case TransferDFragment.RESULT_TRANSFER_NUMBER:
+                    String to = data.getStringExtra("to_number");
+                    transfer = data.getParcelableExtra("transfer");
+                    try {
+                        mCallbacks.getService().transfer(transfer.getCallId(), to);
+                        mCallbacks.getService().hangUp(transfer.getCallId());
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+                case Activity.RESULT_CANCELED:
+                default:
+                    synchronized (mBubbleModel) {
+                        mBubbleModel.clear();
+                    }
+                    initNormalStateDisplay();
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        Log.i(TAG, "onCreateView");
+        final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_call, container, false);
+
+        mBubbleView = (BubblesView) rootView.findViewById(R.id.main_view);
+        //mBubbleView.setFragment(this);
+        mBubbleView.setModel(mBubbleModel);
+        mBubbleView.getHolder().addCallback(this);
+
+        mCallStatusTxt = (TextView) rootView.findViewById(R.id.call_status_txt);
+
+        mSecuritySwitch = (ViewSwitcher) rootView.findViewById(R.id.security_switcher);
+        mToggleSpeakers = (ToggleButton) rootView.findViewById(R.id.speaker_toggle);
+
+        mToggleSpeakers.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                try {
+                    mCallbacks.getService().toggleSpeakerPhone(isChecked);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+
+            }
+        });
+
+        synchronized (mBubbleModel) {
+            mBubbleModel.setSize(mBubbleView.getWidth(), mBubbleView.getHeight() - mToggleSpeakers.getHeight(), bubbleSize);
+        }
+
+        rootView.findViewById(R.id.dialpad_btn).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+                lManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
+            }
+        });
+
+        return rootView;
+    }
+
+    public Conference getConference() {
+        return mCallbacks.getDisplayedConference();
+    }
+
+    private void initNormalStateDisplay() {
+        Log.i(TAG, "Start normal display");
+        synchronized (mBubbleModel) {
+            mCallbacks.startTimer();
+            mBubbleModel.clearAttractors();
+            PointF c = mBubbleModel.getCircleCenter();
+
+            getBubbleForUser(getConference(), (int) c.x, (int) c.y);
+
+            final float angle_part = (float) (2 * Math.PI / getConference().getParticipants().size());
+            final float angle_shift = (float) (Math.PI / 2);
+            float radiusCalls = mBubbleModel.getCircleSize();
+            for (int i = 0; i < getConference().getParticipants().size(); ++i) {
+                SipCall partee = getConference().getParticipants().get(i);
+                if (partee == null) {
+                    continue;
+                }
+                float dX = FloatMath.cos(angle_part * i + angle_shift) * radiusCalls;
+                float dY = FloatMath.sin(angle_part * i + angle_shift) * radiusCalls;
+                getBubbleFor(partee, (int) (c.x + dX), (int) (c.y + dY));
+            }
+        }
+        mBubbleModel.curState = BubbleModel.State.Incall;
+        updateSecurityDisplay();
+    }
+
+    private void updateSecurityDisplay() {
+
+        //First we check if at least one participant use a security layer.
+        if (!getConference().useSecureLayer())
+            return;
+
+        Log.i(TAG, "Enable security display");
+        if (getConference().hasMultipleParticipants()) {
+            //TODO What layout should we put?
+        } else {
+            final SecureSipCall secured = (SecureSipCall) getConference().getParticipants().get(0);
+            switch (secured.displayModule()) {
+                case SecureSipCall.DISPLAY_GREEN_LOCK:
+                    Log.i(TAG, "DISPLAY_GREEN_LOCK");
+                    showLock(R.drawable.green_lock);
+                    break;
+                case SecureSipCall.DISPLAY_RED_LOCK:
+                    Log.i(TAG, "DISPLAY_RED_LOCK");
+                    showLock(R.drawable.red_lock);
+                    break;
+                case SecureSipCall.DISPLAY_CONFIRM_SAS:
+                    final Button sas = (Button) mSecuritySwitch.findViewById(R.id.confirm_sas);
+                    Log.i(TAG, "Confirm SAS: " + secured.getSAS());
+                    sas.setText("Confirm SAS: " + secured.getSAS());
+                    sas.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            try {
+                                mCallbacks.getService().confirmSAS(secured.getCallId());
+                                showLock(R.drawable.green_lock);
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    });
+                    mSecuritySwitch.setDisplayedChild(0);
+                    mSecuritySwitch.setVisibility(View.VISIBLE);
+                    break;
+                case SecureSipCall.DISPLAY_NONE:
+                    break;
+            }
+        }
+    }
+
+    private void showLock(int resId) {
+        ImageView lock = (ImageView) mSecuritySwitch.findViewById(R.id.lock_image);
+        lock.setImageDrawable(getResources().getDrawable(resId));
+        mSecuritySwitch.setDisplayedChild(1);
+        mSecuritySwitch.setVisibility(View.VISIBLE);
+    }
+
+    private void initIncomingCallDisplay() {
+        Log.i(TAG, "Start incoming display");
+        if (getConference().getParticipants().get(0).getAccount().isAutoanswerEnabled()) {
+            try {
+                mCallbacks.getService().accept(getConference().getParticipants().get(0).getCallId());
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            } catch (NullPointerException e) {
+                e.printStackTrace();
+            }
+        } else {
+            getBubbleFor(getConference().getParticipants().get(0), mBubbleModel.getWidth() / 2, 2 * mBubbleModel.getHeight() / 3);
+            synchronized (mBubbleModel) {
+                mBubbleModel.clearAttractors();
+                mBubbleModel.addAttractor(new Attractor(new PointF(3 * mBubbleModel.getWidth() / 4, 2 * mBubbleModel.getHeight() / 3), attractorSize, new Attractor.Callback() {
+                    @Override
+                    public boolean onBubbleSucked(Bubble b) {
+                        if (!accepted) {
+                            try {
+                                mCallbacks.getService().accept(b.getCallID());
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                            accepted = true;
+                        }
+                        return false;
+                    }
+                }, buttonCall));
+                mBubbleModel.addAttractor(new Attractor(new PointF(mBubbleModel.getWidth() / 4, 2 * mBubbleModel.getHeight() / 3), attractorSize, new Attractor.Callback() {
+                    @Override
+                    public boolean onBubbleSucked(Bubble b) {
+                        if (!accepted) {
+                            try {
+                                mCallbacks.getService().refuse(b.getCallID());
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                            accepted = true;
+                        }
+                        return false;
+                    }
+                }, buttonHangUp));
+            }
+            mBubbleModel.curState = BubbleModel.State.Incoming;
+        }
+    }
+
+    private void initOutGoingCallDisplay() {
+        Log.i(TAG, "Start outgoing display");
+        synchronized (mBubbleModel) {
+            PointF c = mBubbleModel.getCircleCenter();
+            float radiusCalls = mBubbleModel.getCircleSize();
+            getBubbleForUser(getConference(), c.x, c.y);
+            int angle_part = 360 / getConference().getParticipants().size();
+            for (int i = 0; i < getConference().getParticipants().size(); ++i) {
+                double dX = Math.cos(Math.toRadians(angle_part * i + 90)) * radiusCalls;
+                double dY = Math.sin(Math.toRadians(angle_part * i + 90)) * radiusCalls;
+                getBubbleFor(getConference().getParticipants().get(i), (int) (c.x + dX), (int) (c.y + dY));
+            }
+            mBubbleModel.clearAttractors();
+        }
+        mBubbleModel.curState = BubbleModel.State.Outgoing;
+    }
+
+    /**
+     * Retrieves or create a bubble for a given contact. If the bubble exists, it is moved to the new location.
+     *
+     * @param call The call associated to a contact
+     * @param x    Initial or new x position.
+     * @param y    Initial or new y position.
+     * @return Bubble corresponding to the contact.
+     */
+    private Bubble getBubbleFor(SipCall call, float x, float y) {
+        Bubble contact_bubble = mBubbleModel.getBubble(call.getCallId());
+        if (contact_bubble != null) {
+            ((BubbleContact) contact_bubble).setCall(call);
+            contact_bubble.attractionPoint.set(x, y);
+            return contact_bubble;
+        }
+
+        contact_bubble = new BubbleContact(getActivity(), call, x, y, bubbleSize);
+
+        mBubbleModel.addBubble(contact_bubble);
+        return contact_bubble;
+    }
+
+    private Bubble getBubbleForUser(Conference conf, float x, float y) {
+        Bubble contact_bubble = mBubbleModel.getUser();
+        if (contact_bubble != null) {
+            contact_bubble.attractionPoint.set(x, y);
+            ((BubbleUser) contact_bubble).setConference(conf);
+
+            return contact_bubble;
+        }
+
+        contact_bubble = new BubbleUser(getActivity(), CallContact.ContactBuilder.buildUserContact(getActivity().getContentResolver()), conf, x, y,
+                bubbleSize * 1.3f);
+/*
+        try {
+            ((BubbleUser) contact_bubble).setMute(mCallbacks.getService().isCaptureMuted());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        } catch (NullPointerException e1) {
+            e1.printStackTrace();
+        }*/
+        mBubbleModel.addBubble(contact_bubble);
+        return contact_bubble;
+    }
+
+    public boolean canOpenIMPanel() {
+        return mBubbleModel.curState == BubbleModel.State.Incall && (mBubbleView == null || !mBubbleView.isDraggingBubble());
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        synchronized (mBubbleModel) {
+            mBubbleModel.setSize(width, height, bubbleSize);
+        }
+        if (getConference().getParticipants().size() == 1) {
+            if (getConference().getParticipants().get(0).isIncoming() && getConference().getParticipants().get(0).isRinging()) {
+                initIncomingCallDisplay();
+            } else if (getConference().getParticipants().get(0).isRinging()) {
+                initOutGoingCallDisplay();
+            } else if (getConference().getParticipants().get(0).isOngoing()) {
+                initNormalStateDisplay();
+            }
+        } else if (getConference().getParticipants().size() > 1) {
+            initNormalStateDisplay();
+        }
+    }
+
+    public void makeTransfer(BubbleContact contact) {
+        FragmentManager fm = getFragmentManager();
+        editName = TransferDFragment.newInstance();
+        Bundle b = new Bundle();
+        try {
+            b.putParcelableArrayList("calls", (ArrayList<Conference>) mCallbacks.getService().getConcurrentCalls());
+            b.putParcelable("call_selected", contact.associated_call);
+            editName.setArguments(b);
+            editName.setTargetFragment(this, REQUEST_TRANSFER);
+            editName.show(fm, "");
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // check that soft input is hidden
+        InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+        lManager.hideSoftInputFromWindow(mBubbleView.getWindowToken(), 0);
+        if (editName != null && editName.isVisible()) {
+            editName.dismiss();
+        }
+    }
+
+    public BubblesView getBubbleView() {
+        return mBubbleView;
+    }
+
+    public void updateTime() {
+        if (getConference() != null) {
+            long duration = System.currentTimeMillis() - getConference().getParticipants().get(0).getTimestampStart_();
+            duration = duration / 1000;
+            if (getConference().isOnGoing())
+                mCallStatusTxt.setText(String.format("%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
+        }
+
+    }
+
+    public void onKeyUp(int keyCode, KeyEvent event) {
+        try {
+
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                case KeyEvent.KEYCODE_VOLUME_UP:
+                    break;
+                default:
+                    String toSend = Character.toString(event.getDisplayLabel());
+                    toSend = toSend.toUpperCase(Locale.getDefault());
+                    Log.d(TAG, "toSend " + toSend);
+                    mCallbacks.getService().playDtmf(toSend);
+                    break;
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        } catch (NullPointerException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/fragments/CallListFragment.java b/ring-android/src/cx/ring/fragments/CallListFragment.java
new file mode 100644
index 0000000..997085e
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/CallListFragment.java
@@ -0,0 +1,450 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipData.Item;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.*;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.DragShadowBuilder;
+import android.view.View.OnDragListener;
+import android.view.ViewGroup;
+import android.widget.*;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+
+import cx.ring.client.CallActivity;
+import cx.ring.client.HomeActivity;
+import cx.ring.model.Conference;
+import cx.ring.service.ISipService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Observable;
+import java.util.Observer;
+
+public class CallListFragment extends CallableWrapperFragment {
+
+    private static final String TAG = CallListFragment.class.getSimpleName();
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private TextView mConversationsTitleTextView;
+    CallListAdapter mConferenceAdapter;
+
+    public static final int REQUEST_TRANSFER = 10;
+    public static final int REQUEST_CONF = 20;
+
+    /**
+     * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
+     */
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            Log.i(TAG, "I'm a dummy");
+            return null;
+        }
+
+    };
+
+    @Override
+    public void callStateChanged(Conference c, String callID, String state) {
+        Log.i(TAG, "callStateChanged" + callID + "    " + state);
+        updateLists();
+    }
+
+    @Override
+    public void confCreated(Conference c, String id) {
+        Log.i(TAG, "confCreated");
+        updateLists();
+    }
+
+    @Override
+    public void confRemoved(Conference c, String id) {
+        Log.i(TAG, "confRemoved");
+        updateLists();
+    }
+
+    @Override
+    public void confChanged(Conference c, String id, String state) {
+        Log.i(TAG, "confChanged");
+        updateLists();
+    }
+
+    @Override
+    public void recordingChanged(Conference c, String callID, String filename) {
+        Log.i(TAG, "confChanged");
+        updateLists();
+    }
+
+    /**
+     * The Activity calling this fragment has to implement this interface
+     */
+    public interface Callbacks {
+        public ISipService getService();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+
+    }
+
+    private Runnable mUpdateTimeTask = new Runnable() {
+        public void run() {
+            final long start = SystemClock.uptimeMillis();
+            long millis = SystemClock.uptimeMillis() - start;
+            int seconds = (int) (millis / 1000);
+            int minutes = seconds / 60;
+            seconds = seconds % 60;
+
+            mConferenceAdapter.notifyDataSetChanged();
+            mHandler.postAtTime(this, start + (((minutes * 60) + seconds + 1) * 1000));
+        }
+    };
+
+    private Handler mHandler = new Handler();
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mCallbacks.getService() != null) {
+
+            updateLists();
+            if (!mConferenceAdapter.isEmpty()) {
+                mHandler.postDelayed(mUpdateTimeTask, 0);
+            }
+        }
+
+    }
+
+    @SuppressWarnings("unchecked")
+    // No proper solution with HashMap runtime cast
+    public void updateLists() {
+        try {
+            HashMap<String, Conference> confs = (HashMap<String, Conference>) mCallbacks.getService().getConferenceList();
+            String newTitle = getResources().getQuantityString(cx.ring.R.plurals.home_conferences_title, confs.size(), confs.size());
+            mConversationsTitleTextView.setText(newTitle);
+            mConferenceAdapter.updateDataset(new ArrayList<Conference>(confs.values()));
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mHandler.removeCallbacks(mUpdateTimeTask);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        Log.i(TAG, "onCreateView");
+        View inflatedView = inflater.inflate(cx.ring.R.layout.frag_call_list, container, false);
+
+        mConversationsTitleTextView = (TextView) inflatedView.findViewById(cx.ring.R.id.confs_counter);
+
+        mConferenceAdapter = new CallListAdapter(getActivity());
+        ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setAdapter(mConferenceAdapter);
+        ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setOnItemClickListener(callClickListener);
+        ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setOnItemLongClickListener(mItemLongClickListener);
+
+        return inflatedView;
+    }
+
+    OnItemClickListener callClickListener = new OnItemClickListener() {
+
+        @Override
+        public void onItemClick(AdapterView<?> arg0, View v, int arg2, long arg3) {
+            Intent intent = new Intent().setClass(getActivity(), CallActivity.class);
+            intent.putExtra("resuming", true);
+            intent.putExtra("conference", (Conference) v.getTag());
+            startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
+        }
+    };
+
+    private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
+
+        @Override
+        public boolean onItemLongClick(AdapterView<?> adptv, View view, int pos, long arg3) {
+            final Vibrator vibe = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE);
+            vibe.vibrate(80);
+            Intent i = new Intent();
+            Bundle b = new Bundle();
+            b.putParcelable("conference", (Conference) adptv.getAdapter().getItem(pos));
+            i.putExtra("bconference", b);
+
+            DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
+            ClipData data = ClipData.newIntent("conference", i);
+            view.startDrag(data, shadowBuilder, view, 0);
+            return false;
+        }
+
+    };
+
+    public class CallListAdapter extends BaseAdapter implements Observer {
+
+        private ArrayList<Conference> calls;
+
+        private Context mContext;
+
+        public CallListAdapter(Context act) {
+            super();
+            mContext = act;
+            calls = new ArrayList<Conference>();
+
+        }
+
+        public void updateDataset(ArrayList<Conference> list) {
+            calls.clear();
+            calls.addAll(list);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return calls.size();
+        }
+
+        @Override
+        public Conference getItem(int position) {
+            return calls.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null)
+                convertView = LayoutInflater.from(mContext).inflate(cx.ring.R.layout.item_calllist, null);
+
+            Conference call = calls.get(position);
+            if (call.getParticipants().size() == 1) {
+                ((TextView) convertView.findViewById(cx.ring.R.id.call_title)).setText(call.getParticipants().get(0).getmContact().getmDisplayName());
+
+                long duration = (System.currentTimeMillis() - (call.getParticipants().get(0).getTimestampStart_())) / 1000;
+
+                ((TextView) convertView.findViewById(cx.ring.R.id.call_time)).setText(String.format("%d:%02d:%02d", duration / 3600, (duration % 3600) / 60,
+                        (duration % 60)));
+            } else {
+//                String tmp = "Conference with " + call.getParticipants().size() + " participants";
+                ((TextView) convertView.findViewById(cx.ring.R.id.call_title)).setText(getString(cx.ring.R.string.home_conf_item, call.getParticipants().size()));
+            }
+            // ((TextView) convertView.findViewById(R.id.num_participants)).setText("" + call.getParticipants().size());
+            ((TextView) convertView.findViewById(cx.ring.R.id.call_status)).setText(call.getState());
+
+            convertView.setOnDragListener(dragListener);
+            convertView.setTag(call);
+
+            return convertView;
+        }
+
+        @Override
+        public void update(Observable observable, Object data) {
+            Log.i(TAG, "Updating views...");
+            notifyDataSetChanged();
+        }
+
+    }
+
+    OnDragListener dragListener = new OnDragListener() {
+
+        @SuppressWarnings("deprecation")
+        // deprecated in API 16....
+        @Override
+        public boolean onDrag(View v, DragEvent event) {
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    // Do nothing
+                    // Log.w(TAG, "ACTION_DRAG_STARTED");
+                    break;
+                case DragEvent.ACTION_DRAG_ENTERED:
+                    // Log.w(TAG, "ACTION_DRAG_ENTERED");
+                    v.setBackgroundColor(Color.GREEN);
+                    break;
+                case DragEvent.ACTION_DRAG_EXITED:
+                    // Log.w(TAG, "ACTION_DRAG_EXITED");
+                    v.setBackgroundDrawable(getResources().getDrawable(cx.ring.R.drawable.item_generic_selector));
+                    break;
+                case DragEvent.ACTION_DROP:
+                    // Log.w(TAG, "ACTION_DROP");
+                    View view = (View) event.getLocalState();
+
+                    Item i = event.getClipData().getItemAt(0);
+                    Intent intent = i.getIntent();
+                    intent.setExtrasClassLoader(Conference.class.getClassLoader());
+
+                    Conference initial = (Conference) view.getTag();
+                    Conference target = (Conference) v.getTag();
+
+                    if (initial == target) {
+                        return true;
+                    }
+
+                    DropActionsChoice dialog = DropActionsChoice.newInstance();
+                    Bundle b = new Bundle();
+                    b.putParcelable("call_initial", initial);
+                    b.putParcelable("call_targeted", target);
+                    dialog.setArguments(b);
+                    dialog.setTargetFragment(CallListFragment.this, 0);
+                    dialog.show(getFragmentManager(), "dialog");
+
+                    // view.setBackgroundColor(Color.WHITE);
+                    // v.setBackgroundColor(Color.BLACK);
+                    break;
+                case DragEvent.ACTION_DRAG_ENDED:
+                    // Log.w(TAG, "ACTION_DRAG_ENDED");
+                    View view1 = (View) event.getLocalState();
+                    view1.setVisibility(View.VISIBLE);
+                    v.setBackgroundDrawable(getResources().getDrawable(cx.ring.R.drawable.item_generic_selector));
+                default:
+                    break;
+            }
+            return true;
+        }
+
+    };
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        Conference transfer;
+        if (requestCode == REQUEST_TRANSFER) {
+            switch (resultCode) {
+                case 0:
+                    Conference c = data.getParcelableExtra("target");
+                    transfer = data.getParcelableExtra("transfer");
+                    try {
+                        mCallbacks.getService().attendedTransfer(transfer.getParticipants().get(0).getCallId(), c.getParticipants().get(0).getCallId());
+                        mConferenceAdapter.notifyDataSetChanged();
+                    } catch (RemoteException e) {
+                        // TODO Auto-generated catch block
+                        e.printStackTrace();
+                    }
+                    Toast.makeText(getActivity(), getString(cx.ring.R.string.home_transfer_complet), Toast.LENGTH_LONG).show();
+                    break;
+
+                case 1:
+                    String to = data.getStringExtra("to_number");
+                    transfer = data.getParcelableExtra("transfer");
+                    try {
+                        Toast.makeText(getActivity(), getString(cx.ring.R.string.home_transfering, transfer.getParticipants().get(0).getmContact().getmDisplayName(), to),
+                                Toast.LENGTH_SHORT).show();
+                        mCallbacks.getService().transfer(transfer.getParticipants().get(0).getCallId(), to);
+                        mCallbacks.getService().hangUp(transfer.getParticipants().get(0).getCallId());
+                    } catch (RemoteException e) {
+                        // TODO Auto-generated catch block
+                        e.printStackTrace();
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        } else if (requestCode == REQUEST_CONF) {
+            switch (resultCode) {
+                case 0:
+                    Conference call_to_add = data.getParcelableExtra("transfer");
+                    Conference call_target = data.getParcelableExtra("target");
+
+                    bindCalls(call_to_add, call_target);
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+
+    private void bindCalls(Conference call_to_add, Conference call_target) {
+        try {
+
+            Log.i(TAG, "joining calls:" + call_to_add.getId() + " and " + call_target.getId());
+
+            if (call_target.hasMultipleParticipants() && !call_to_add.hasMultipleParticipants()) {
+
+                mCallbacks.getService().addParticipant(call_to_add.getParticipants().get(0), call_target.getId());
+
+            } else if (call_target.hasMultipleParticipants() && call_to_add.hasMultipleParticipants()) {
+
+                // We join two conferences
+                mCallbacks.getService().joinConference(call_to_add.getId(), call_target.getId());
+
+            } else if (!call_target.hasMultipleParticipants() && call_to_add.hasMultipleParticipants()) {
+
+                mCallbacks.getService().addParticipant(call_target.getParticipants().get(0), call_to_add.getId());
+
+            } else {
+                // We join two single calls to create a conf
+                mCallbacks.getService().joinParticipant(call_to_add.getParticipants().get(0).getCallId(),
+                        call_target.getParticipants().get(0).getCallId());
+            }
+
+        } catch (RemoteException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/CallableWrapperFragment.java b/ring-android/src/cx/ring/fragments/CallableWrapperFragment.java
new file mode 100644
index 0000000..53ccc01
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/CallableWrapperFragment.java
@@ -0,0 +1,184 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import cx.ring.interfaces.CallInterface;
+import cx.ring.model.Conference;
+import cx.ring.service.CallManagerCallBack;
+
+import java.util.HashMap;
+
+public abstract class CallableWrapperFragment extends Fragment implements CallInterface {
+
+
+    private CallReceiver mReceiver;
+
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mReceiver = new CallReceiver();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(CallManagerCallBack.INCOMING_CALL);
+        intentFilter.addAction(CallManagerCallBack.INCOMING_TEXT);
+        intentFilter.addAction(CallManagerCallBack.CALL_STATE_CHANGED);
+        intentFilter.addAction(CallManagerCallBack.CONF_CREATED);
+        intentFilter.addAction(CallManagerCallBack.CONF_REMOVED);
+        intentFilter.addAction(CallManagerCallBack.CONF_CHANGED);
+        intentFilter.addAction(CallManagerCallBack.RECORD_STATE_CHANGED);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_OFF);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_ON);
+        intentFilter.addAction(CallManagerCallBack.DISPLAY_SAS);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_NOT_SUPPORTED);
+        intentFilter.addAction(CallManagerCallBack.RTCP_REPORT_RECEIVED);
+        getActivity().registerReceiver(mReceiver, intentFilter);
+    }
+
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        getActivity().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void callStateChanged(Conference c, String callID, String state) {
+
+    }
+
+    @Override
+    public void incomingText(Conference c, String ID, String from, String msg) {
+
+    }
+
+    @Override
+    public void confCreated(Conference c, String id) {
+
+    }
+
+    @Override
+    public void confRemoved(Conference c, String id) {
+
+    }
+
+    @Override
+    public void confChanged(Conference c, String id, String state) {
+
+    }
+
+    @Override
+    public void recordingChanged(Conference c, String callID, String filename) {
+
+    }
+
+    @Override
+    public void secureZrtpOn(Conference c, String id) {
+
+    }
+
+    @Override
+    public void secureZrtpOff(Conference c, String id) {
+
+    }
+
+    @Override
+    public void displaySAS(Conference c, String securedCallID) {
+
+    }
+
+    @Override
+    public void zrtpNegotiationFailed(Conference c, String securedCallID) {
+
+    }
+
+    @Override
+    public void zrtpNotSupported(Conference c, String securedCallID) {
+
+    }
+
+    @Override
+    public void rtcpReportReceived(Conference c, HashMap<String, Integer> stats) {
+
+    }
+
+
+    public class CallReceiver extends BroadcastReceiver {
+
+        private final String TAG = CallReceiver.class.getSimpleName();
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().contentEquals(CallManagerCallBack.INCOMING_TEXT)) {
+                incomingText((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("CallID"), intent.getStringExtra("From"), intent.getStringExtra("Msg"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.CALL_STATE_CHANGED)) {
+                callStateChanged((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("CallID"), intent.getStringExtra("State"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.CONF_CREATED)) {
+                confCreated((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("confID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.CONF_REMOVED)) {
+                confRemoved((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("confID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.CONF_CHANGED)) {
+                confChanged((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("confID"), intent.getStringExtra("state"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.RECORD_STATE_CHANGED)) {
+                recordingChanged((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"), intent.getStringExtra("file"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_OFF)) {
+                secureZrtpOff((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_ON)) {
+                secureZrtpOn((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.DISPLAY_SAS)) {
+                displaySAS((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED)) {
+                zrtpNegotiationFailed((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_NOT_SUPPORTED)) {
+                zrtpNotSupported((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("callID"));
+            } else if (intent.getAction().contentEquals(CallManagerCallBack.RTCP_REPORT_RECEIVED)) {
+                rtcpReportReceived(null, null); // FIXME
+            } else {
+                Log.e(TAG, "Unknown action: " + intent.getAction());
+            }
+
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/fragments/ConferenceDFragment.java b/ring-android/src/cx/ring/fragments/ConferenceDFragment.java
new file mode 100644
index 0000000..7bfbafa
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/ConferenceDFragment.java
@@ -0,0 +1,163 @@
+package cx.ring.fragments;
+
+import java.util.ArrayList;
+
+import cx.ring.R;
+import cx.ring.loaders.ContactsLoader;
+import cx.ring.model.Conference;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class ConferenceDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Bundle> {
+
+
+    SimpleCallListAdapter mAdapter;
+
+    /**
+     * Create a new instance of CallActionsDFragment
+     */
+    public static ConferenceDFragment newInstance() {
+        ConferenceDFragment f = new ConferenceDFragment();
+        return f;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Pick a style based on the num.
+        int style = DialogFragment.STYLE_NORMAL, theme = 0;
+        setStyle(style, theme);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_conference, null);
+
+        ArrayList<Conference> calls = getArguments().getParcelableArrayList("calls");
+        final Conference call_selected = getArguments().getParcelable("call_selected");
+
+        mAdapter = new SimpleCallListAdapter(getActivity(), calls);
+        ListView list = (ListView) rootView.findViewById(R.id.concurrent_calls);
+        list.setAdapter(mAdapter);
+        list.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+
+                Intent in = new Intent();
+                
+                in.putExtra("transfer", call_selected);
+                in.putExtra("target", mAdapter.getItem(pos));
+                getTargetFragment().onActivityResult(getTargetRequestCode(), 0, in);
+                dismiss();
+            }
+        });
+        list.setEmptyView(rootView.findViewById(R.id.empty_view));
+
+        
+
+        final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView).setTitle("Transfer " + call_selected.getParticipants().get(0).getmContact())
+                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+
+                        dismiss();
+                    }
+                }).create();
+
+        return a;
+    }
+
+    @Override
+    public Loader<Bundle> onCreateLoader(int id, Bundle args) {
+        Uri baseUri;
+
+        if (args != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(args.getString("filter")));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+        ContactsLoader l = new ContactsLoader(getActivity(), baseUri);
+        l.forceLoad();
+        return l;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
+
+//        ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts");
+
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Bundle> loader) {
+        // Thi is called when the last Cursor provided to onLoadFinished
+        // mListAdapter.swapCursor(null);
+    }
+
+    
+
+    private class SimpleCallListAdapter extends BaseAdapter {
+
+        private LayoutInflater mInflater;
+        ArrayList<Conference> calls;
+
+        public SimpleCallListAdapter(final Context context, ArrayList<Conference> calls2) {
+            super();
+            mInflater = LayoutInflater.from(context);
+            calls = calls2;
+        }
+
+        @Override
+        public View getView(final int position, final View convertView, final ViewGroup parent) {
+            final TextView tv;
+            if (convertView != null) {
+                tv = (TextView) convertView;
+            } else {
+                tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
+            }
+
+            if(calls.get(position).getParticipants().size() == 1){
+                tv.setText(calls.get(position).getParticipants().get(0).getmContact().getmDisplayName());
+            } else {
+                tv.setText("Conference with "+ calls.get(position).getParticipants().size() + " participants");
+            }
+            
+            return tv;
+        }
+
+        @Override
+        public int getCount() {
+            return calls.size();
+        }
+
+        @Override
+        public Conference getItem(int pos) {
+            return calls.get(pos);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/ContactListFragment.java b/ring-android/src/cx/ring/fragments/ContactListFragment.java
new file mode 100644
index 0000000..9640452
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/ContactListFragment.java
@@ -0,0 +1,404 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *          Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import java.util.ArrayList;
+
+import cx.ring.R;
+import cx.ring.adapters.ContactsAdapter;
+import cx.ring.adapters.StarredContactsAdapter;
+import cx.ring.loaders.ContactsLoader;
+import cx.ring.loaders.LoaderConstants;
+import cx.ring.model.CallContact;
+import cx.ring.service.ISipService;
+import cx.ring.views.SwipeListViewTouchListener;
+import cx.ring.views.stickylistheaders.StickyListHeadersListView;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.DragShadowBuilder;
+import android.view.View.MeasureSpec;
+import android.view.View.OnClickListener;
+import android.view.View.OnDragListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.GridView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
+
+public class ContactListFragment extends Fragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Bundle> {
+    private static final String TAG = "ContactListFragment";
+    ContactsAdapter mListAdapter;
+    StarredContactsAdapter mGridAdapter;
+    SearchView mQuickReturnSearchView;
+    String mCurFilter;
+    StickyListHeadersListView mContactList;
+    private GridView mStarredGrid;
+    private SwipeListViewTouchListener mSwipeLvTouchListener;
+    private LinearLayout mHeader;
+
+    @Override
+    public void onCreate(Bundle savedInBundle) {
+        super.onCreate(savedInBundle);
+        mGridAdapter = new StarredContactsAdapter(getActivity());
+        mListAdapter = new ContactsAdapter(this);
+    }
+
+    public Callbacks mCallbacks = sDummyCallbacks;
+    private LinearLayout llMain;
+    /**
+     * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
+     */
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+        @Override
+        public void onCallContact(CallContact c) {
+        }
+
+        @Override
+        public void onTextContact(CallContact c) {
+        }
+
+        @Override
+        public void onEditContact(CallContact c) {
+        }
+
+        @Override
+        public ISipService getService() {
+            Log.i(TAG, "Dummy");
+            return null;
+        }
+
+        @Override
+        public void onContactDragged() {
+        }
+
+        @Override
+        public void toggleDrawer() {
+        }
+
+        @Override
+        public void setDragView(RelativeLayout relativeLayout) {
+            
+        }
+
+        @Override
+        public void toggleForSearchDrawer() {
+        }
+    };
+
+    public interface Callbacks {
+        void onCallContact(CallContact c);
+
+        void onTextContact(CallContact c);
+
+        public ISipService getService();
+
+        void onContactDragged();
+
+        void toggleDrawer();
+
+        void onEditContact(CallContact item);
+
+        void setDragView(RelativeLayout relativeLayout);
+
+        void toggleForSearchDrawer();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+        
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_contact_list, container, false);
+        mHeader = (LinearLayout) inflater.inflate(R.layout.frag_contact_list_header, null);
+        mContactList = (StickyListHeadersListView) inflatedView.findViewById(R.id.contacts_stickylv);
+
+        inflatedView.findViewById(R.id.drag_view).setOnTouchListener(new OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                return true;
+            }
+        });
+
+        inflatedView.findViewById(R.id.contact_search_button).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                mContactList.smoothScrollToPosition(0);
+                mQuickReturnSearchView.setOnQueryTextListener(ContactListFragment.this);
+                mQuickReturnSearchView.setIconified(false);
+                mQuickReturnSearchView.setFocusable(true);
+                mCallbacks.toggleForSearchDrawer();
+            }
+        });
+
+        inflatedView.findViewById(R.id.slider_button).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                mCallbacks.toggleDrawer();
+            }
+        });
+        
+        mCallbacks.setDragView(((RelativeLayout) inflatedView.findViewById(R.id.slider_button)));
+
+        mQuickReturnSearchView = (SearchView) mHeader.findViewById(R.id.contact_search);
+        mStarredGrid = (GridView) mHeader.findViewById(R.id.favorites_grid);
+        llMain = (LinearLayout) mHeader.findViewById(R.id.llMain);
+        return inflatedView;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mContactList.addHeaderView(mHeader, null, false);
+        mContactList.setAdapter(mListAdapter);
+
+        mStarredGrid.setAdapter(mGridAdapter);
+        mQuickReturnSearchView.setIconifiedByDefault(false);
+
+        mQuickReturnSearchView.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                mQuickReturnSearchView.setIconified(false);
+                mQuickReturnSearchView.setFocusable(true);
+            }
+        });
+        mQuickReturnSearchView.setOnQueryTextListener(ContactListFragment.this);
+
+        getLoaderManager().initLoader(LoaderConstants.CONTACT_LOADER, null, this);
+
+    }
+
+    private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
+        @Override
+        public boolean onItemLongClick(AdapterView<?> av, View view, int pos, long id) {
+            DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view.findViewById(R.id.photo));
+            view.startDrag(null, shadowBuilder, view, 0);
+            mCallbacks.onContactDragged();
+            return true;
+        }
+
+    };
+
+    private void setGridViewListeners() {
+        mStarredGrid.setOnDragListener(dragListener);
+        mStarredGrid.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View v, int pos, long arg3) {
+                mCallbacks.onCallContact(mGridAdapter.getItem(pos));
+            }
+        });
+        mStarredGrid.setOnItemLongClickListener(mItemLongClickListener);
+    }
+
+    private void setListViewListeners() {
+        mSwipeLvTouchListener = new SwipeListViewTouchListener(mContactList.getWrappedList(), new SwipeListViewTouchListener.OnSwipeCallback() {
+            @Override
+            public void onSwipeLeft(ListView listView, int[] reverseSortedPositions) {
+            }
+
+            @Override
+            public void onSwipeRight(ListView listView, View down) {
+                down.findViewById(R.id.quick_edit).setClickable(true);
+                down.findViewById(R.id.quick_discard).setClickable(true);
+                down.findViewById(R.id.quick_starred).setClickable(true);
+
+            }
+        }, true, false);
+
+        mContactList.getWrappedList().setOnDragListener(dragListener);
+        mContactList.getWrappedList().setOnTouchListener(mSwipeLvTouchListener);
+        mContactList.getWrappedList().setOnItemLongClickListener(mItemLongClickListener);
+        mContactList.getWrappedList().setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View view, int pos, long id) {
+                Log.i(TAG, "Opening Item");
+                mSwipeLvTouchListener.openItem(view, pos, id);
+            }
+        });
+    }
+
+    OnDragListener dragListener = new OnDragListener() {
+
+        @Override
+        public boolean onDrag(View v, DragEvent event) {
+            switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                // Do nothing
+                break;
+            case DragEvent.ACTION_DRAG_ENTERED:
+                break;
+            case DragEvent.ACTION_DRAG_EXITED:
+                // v.setBackgroundDrawable(null);
+                break;
+            case DragEvent.ACTION_DROP:
+                break;
+            case DragEvent.ACTION_DRAG_ENDED:
+                View view1 = (View) event.getLocalState();
+                view1.setVisibility(View.VISIBLE);
+            default:
+                break;
+            }
+            return true;
+        }
+
+    };
+
+    @Override
+    public boolean onQueryTextChange(String newText) {
+        if (newText.isEmpty()) {
+            getLoaderManager().restartLoader(LoaderConstants.CONTACT_LOADER, null, this);
+            return true;
+        }
+        mCurFilter = newText;
+        Bundle b = new Bundle();
+        b.putString("filter", mCurFilter);
+        getLoaderManager().restartLoader(LoaderConstants.CONTACT_LOADER, b, this);
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        // Return false to let the SearchView perform the default action
+        return false;
+    }
+
+    @Override
+    public Loader<Bundle> onCreateLoader(int id, Bundle args) {
+        Uri baseUri;
+
+        Log.i(TAG, "createLoader");
+
+        if (args != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(args.getString("filter")));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+        ContactsLoader l = new ContactsLoader(getActivity(), baseUri);
+        l.forceLoad();
+        return l;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
+
+        mGridAdapter.removeAll();
+        mListAdapter.clear();
+        ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts");
+        ArrayList<CallContact> tmp2 = data.getParcelableArrayList("Starred");
+        mListAdapter.addAll(tmp);
+        mGridAdapter.addAll(tmp2);
+
+        setListViewListeners();
+        setGridViewListeners();
+
+        mStarredGrid.post(new Runnable() {
+
+            @Override
+            public void run() {
+                setGridViewHeight(mStarredGrid, llMain);
+            }
+        });
+
+    }
+
+    // Sets the GridView holder's height to fully expand it
+    public void setGridViewHeight(GridView gridView, LinearLayout llMain) {
+        ListAdapter listAdapter = gridView.getAdapter();
+        if (listAdapter == null) {
+            return;
+        }
+
+        int totalHeight = 0;
+        int firstHeight = 0;
+        int desiredWidth = MeasureSpec.makeMeasureSpec(gridView.getWidth(), MeasureSpec.AT_MOST);
+
+        int rows = (listAdapter.getCount() + gridView.getNumColumns() - 1) / gridView.getNumColumns();
+
+        for (int i = 0; i < rows; i++) {
+            if (i == 0) {
+                View listItem = listAdapter.getView(i, null, gridView);
+                listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
+                firstHeight = listItem.getMeasuredHeight();
+            }
+            totalHeight += firstHeight;
+        }
+
+        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) llMain.getLayoutParams();
+
+        params.height = (int) (totalHeight + (getResources().getDimension(R.dimen.contact_vertical_spacing) * (rows - 1) + llMain.getPaddingBottom() + llMain.getPaddingTop()));
+        llMain.setLayoutParams(params);
+        mHeader.requestLayout();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Bundle> loader) {
+    }
+}
diff --git a/ring-android/src/cx/ring/fragments/DetailsHistoryEntryFragment.java b/ring-android/src/cx/ring/fragments/DetailsHistoryEntryFragment.java
new file mode 100644
index 0000000..0ca2b98
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/DetailsHistoryEntryFragment.java
@@ -0,0 +1,263 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.*;
+import cx.ring.R;
+import cx.ring.adapters.ContactPictureTask;
+import cx.ring.history.HistoryCall;
+import cx.ring.history.HistoryEntry;
+import cx.ring.model.account.Account;
+import cx.ring.model.SipCall;
+import cx.ring.service.ISipService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.NavigableMap;
+import java.util.Random;
+
+public class DetailsHistoryEntryFragment extends Fragment {
+
+    DetailHistoryAdapter mAdapter;
+    HistoryEntry toDisplay;
+    @SuppressWarnings("unused")
+    private static final String TAG = DetailsHistoryEntryFragment.class.getSimpleName();
+    ContactPictureTask tasker;
+
+    private ListView lvMain;
+    private LinearLayout llMain;
+    private RelativeLayout iv;
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+        @Override
+        public void onCall(SipCall call) {
+        }
+
+    };
+
+    public interface Callbacks {
+
+        public ISipService getService();
+
+        public void onCall(SipCall call);
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        toDisplay = (HistoryEntry) getArguments().get("entry");
+        mAdapter = new DetailHistoryAdapter(toDisplay.getCalls(), getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_history_detail, parent, false);
+
+        llMain = (LinearLayout) inflatedView.findViewById(R.id.llMain);
+        /*llMainHolder = (LinearLayout) inflatedView.findViewById(R.id.llMainHolder);*/
+        lvMain = (ListView) inflatedView.findViewById(R.id.lvMain);
+        lvMain.setAdapter(mAdapter);
+        iv = (RelativeLayout) inflatedView.findViewById(R.id.iv);
+
+        ((TextView) iv.findViewById(R.id.history_call_name)).setText(toDisplay.getContact().getmDisplayName());
+
+        tasker = new ContactPictureTask(getActivity(), (ImageView) inflatedView.findViewById(R.id.contact_photo), toDisplay.getContact());
+        tasker.run();
+//        ((TextView) iv.findViewById(R.id.history_entry_number)).setText(getString(R.string.detail_hist_call_number, toDisplay.getNumber()));
+        iv.findViewById(R.id.history_call_name).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                try {
+                    HashMap<String, String> details = (HashMap<String, String>) mCallbacks.getService().getAccountDetails(toDisplay.getAccountID());
+                    ArrayList<HashMap<String, String>> creds = (ArrayList<HashMap<String, String>>) mCallbacks.getService().getCredentials(toDisplay.getAccountID());
+                    Bundle args = new Bundle();
+                    args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt())));
+                    args.putParcelable(SipCall.ACCOUNT, new Account(toDisplay.getAccountID(), details, creds));
+                    args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_RINGING);
+                    args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING);
+                    args.putParcelable(SipCall.CONTACT, toDisplay.getContact());
+
+                    mCallbacks.onCall(new SipCall(args));
+
+                } catch (RemoteException e) {
+                    // TODO Bloc catch généré automatiquement
+                    e.printStackTrace();
+                }
+            }
+        });
+        return inflatedView;
+    }
+
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+    }
+
+    private class DetailHistoryAdapter extends BaseAdapter implements ListAdapter {
+
+        ArrayList<HistoryCall> dataset;
+        Context mContext;
+
+        public DetailHistoryAdapter(NavigableMap<Long, HistoryCall> calls, Context c) {
+            dataset = new ArrayList<HistoryCall>(calls.descendingMap().values());
+            mContext = c;
+        }
+
+        @Override
+        public int getCount() {
+            return dataset.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return dataset.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            HistoryCallView entryView = null;
+
+            if (convertView == null) {
+                // Get a new instance of the row layout view
+                LayoutInflater inflater = LayoutInflater.from(mContext);
+                convertView = inflater.inflate(R.layout.item_history_call, null);
+
+                // Hold the view objects in an object
+                // so they don't need to be re-fetched
+                entryView = new HistoryCallView();
+                entryView.historyCallState = (TextView) convertView.findViewById(R.id.history_call_state);
+                entryView.formatted_date = (TextView) convertView.findViewById(R.id.history_call_date_formatted);
+                entryView.formatted_hour = (TextView) convertView.findViewById(R.id.history_call_hour);
+                entryView.record = (Button) convertView.findViewById(R.id.history_call_record);
+                entryView.duration = (TextView) convertView.findViewById(R.id.history_call_duration);
+
+                convertView.setTag(entryView);
+            } else {
+                entryView = (HistoryCallView) convertView.getTag();
+            }
+
+            final HistoryCall item = dataset.get(position);
+
+            entryView.historyCallState.setText(item.getDirection());
+            entryView.formatted_date.setText(item.getDate());
+            entryView.duration.setText(item.getDurationString());
+            entryView.formatted_hour.setText(item.getStartString("h:mm a"));
+            if (item.isIncoming() && item.isMissed())
+                convertView.setBackgroundColor(getResources().getColor(R.color.holo_red_light));
+
+            if (item.hasRecord()) {
+                entryView.record.setVisibility(View.VISIBLE);
+                entryView.record.setTag(R.id.history_call_record, true);
+                entryView.record.setOnClickListener(new OnClickListener() {
+
+                    @Override
+                    public void onClick(View v) {
+                        try {
+                            if ((Boolean) v.getTag(R.id.history_call_record)) {
+                                mCallbacks.getService().startRecordedFilePlayback(item.getRecordPath());
+                                v.setTag(R.id.replay, false);
+                                ((Button) v).setText(getString(R.string.hist_replay_button_stop));
+                            } else {
+                                mCallbacks.getService().stopRecordedFilePlayback(item.getRecordPath());
+                                v.setTag(R.id.history_call_record, true);
+                                ((Button) v).setText(getString(R.string.hist_replay_button));
+                            }
+                        } catch (RemoteException e) {
+                            // TODO Auto-generated catch block
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+
+            return convertView;
+        }
+
+        /**
+         * ******************
+         * ViewHolder Pattern
+         * *******************
+         */
+        public class HistoryCallView {
+            protected TextView historyCallState;
+            protected TextView formatted_date;
+            protected TextView formatted_hour;
+            protected Button record;
+            protected TextView duration;
+        }
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/DialingFragment.java b/ring-android/src/cx/ring/fragments/DialingFragment.java
new file mode 100644
index 0000000..7a634f9
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/DialingFragment.java
@@ -0,0 +1,234 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import java.util.Locale;
+
+import android.support.v4.app.Fragment;
+import cx.ring.R;
+import cx.ring.service.ISipService;
+import cx.ring.views.ClearableEditText;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class DialingFragment extends Fragment implements OnTouchListener {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = DialingFragment.class.getSimpleName();
+
+    ClearableEditText textField;
+    private Callbacks mCallbacks = sDummyCallbacks;
+
+    /**
+     * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
+     */
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+        @Override
+        public void onCallDialed(String to) {
+        }
+
+        @Override
+        public ISipService getService() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+    };
+
+    /**
+     * The Activity calling this fragment has to implement this interface
+     * 
+     */
+    public interface Callbacks {
+        void onCallDialed(String account);
+
+        public ISipService getService();
+
+    }
+
+    @Override
+    public void setUserVisibleHint(boolean isVisibleToUser) {
+        super.setUserVisibleHint(isVisibleToUser);
+        if (!isVisibleToUser && isAdded()) {
+            InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+            textField.setError(null);
+            textField.getEdit_text().setText("");
+            lManager.hideSoftInputFromWindow(textField.getWindowToken(), 0);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_dialing, parent, false);
+
+        textField = (ClearableEditText) inflatedView.findViewById(R.id.textField);
+        inflatedView.findViewById(R.id.buttonCall).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                String to = textField.getText().toString();
+                if (to.contentEquals("")) {
+                    textField.setError(getString(R.string.dial_error_no_number_dialed));
+                } else {
+                    mCallbacks.onCallDialed(to);
+                }
+            }
+        });
+
+        inflatedView.setOnTouchListener(this);
+
+        inflatedView.findViewById(R.id.alphabetic_keyboard).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                textField.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+                InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+                lManager.showSoftInput(textField.getEdit_text(), 0);
+            }
+        });
+
+        inflatedView.findViewById(R.id.numeric_keyboard).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                textField.setInputType(EditorInfo.TYPE_CLASS_PHONE);
+                InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+                lManager.showSoftInput(textField.getEdit_text(), 0);
+            }
+        });
+        
+        textField.setOnEditorActionListener(new OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                String to = textField.getText().toString();
+                if (to.contentEquals("")) {
+                    textField.setError(getString(R.string.dial_error_no_number_dialed));
+                } else {
+                    mCallbacks.onCallDialed(to);
+                }
+                return true;
+            }
+        });
+        return inflatedView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        textField.getEdit_text().setText("");
+        textField.setTextWatcher(dtmfKeyListener);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        textField.unsetTextWatcher();
+    }
+
+    TextWatcher dtmfKeyListener = new TextWatcher() {
+
+        @Override
+        public void afterTextChanged(Editable s) {
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            if (count - before > 1 || count == 0)
+                return; // pasted a number (not implemented yet)
+
+            try {
+                String toSend = Character.toString(s.charAt(start));
+                toSend.toUpperCase(Locale.getDefault());
+                mCallbacks.getService().playDtmf(toSend);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    };
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+        textField.setError(null);
+        lManager.hideSoftInputFromWindow(textField.getWindowToken(), 0);
+        return false;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/DropActionsChoice.java b/ring-android/src/cx/ring/fragments/DropActionsChoice.java
new file mode 100644
index 0000000..5e09f23
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/DropActionsChoice.java
@@ -0,0 +1,114 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.support.v4.app.DialogFragment;
+import cx.ring.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+public class DropActionsChoice extends DialogFragment {
+
+    ListAdapter mAdapter;
+    private Bundle args;
+    public static final int REQUEST_TRANSFER = 10;
+    public static final int REQUEST_CONF = 20;
+
+    /**
+     * Create a new instance of CallActionsDFragment
+     */
+    public static DropActionsChoice newInstance() {
+        DropActionsChoice f = new DropActionsChoice();
+        return f;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Pick a style based on the num.
+        int style = DialogFragment.STYLE_NORMAL, theme = 0;
+        setStyle(style, theme);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        ListView rootView = new ListView(getActivity());
+
+        args = getArguments();
+        mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.drop_actions));
+
+        // ListView list = (ListView) rootView.findViewById(R.id.concurrent_calls);
+        rootView.setAdapter(mAdapter);
+        rootView.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+                Intent in = new Intent();
+
+                in.putExtra("transfer", args.getParcelable("call_initial"));
+                in.putExtra("target", args.getParcelable("call_targeted"));
+
+                switch (pos) {
+                case 0: // Transfer
+                    getTargetFragment().onActivityResult(REQUEST_TRANSFER, 0, in);
+                    break;
+                case 1: // Conference
+                    getTargetFragment().onActivityResult(REQUEST_CONF, 0, in);
+                    break;
+                }
+                dismiss();
+
+            }
+        });
+
+        final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView).setTitle("Choose Action")
+                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        dismiss();
+                    }
+                }).create();
+
+        return a;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/GeneralAccountFragment.java b/ring-android/src/cx/ring/fragments/GeneralAccountFragment.java
new file mode 100644
index 0000000..6e9a5f2
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/GeneralAccountFragment.java
@@ -0,0 +1,164 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import cx.ring.R;
+import cx.ring.model.account.AccountDetail;
+import cx.ring.model.account.AccountDetailBasic;
+import cx.ring.model.account.Account;
+import cx.ring.views.PasswordPreference;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+public class GeneralAccountFragment extends PreferenceFragment {
+
+    private static final String TAG = GeneralAccountFragment.class.getSimpleName();
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public Account getAccount() {
+            return null;
+        }
+
+    };
+
+    public interface Callbacks {
+
+        public Account getAccount();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.account_general_prefs);
+        setPreferenceDetails(mCallbacks.getAccount().getBasicDetails());
+        addPreferenceListener(mCallbacks.getAccount().getBasicDetails(), changeBasicPreferenceListener);
+
+    }
+
+    private void setPreferenceDetails(AccountDetail details) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "setPreferenceDetails: pref " + p.mKey + " value " + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+                if (!p.isTwoState) {
+                    ((EditTextPreference) pref).setText(p.mValue);
+                    if (pref instanceof PasswordPreference) {
+                        String tmp = "";
+                        for (int i = 0; i < p.mValue.length(); ++i) {
+                            tmp += "*";
+                        }
+                        pref.setSummary(tmp);
+                    } else {
+                        pref.setSummary(p.mValue);
+                    }
+                } else {
+                    Log.i(TAG, "pref:"+p.mKey);
+                    ((CheckBoxPreference) pref).setChecked(p.isChecked());
+                }
+            } else {
+                Log.w(TAG, "pref not found");
+            }
+        }
+    }
+
+    private void addPreferenceListener(AccountDetail details, OnPreferenceChangeListener listener) {
+        for (AccountDetail.PreferenceEntry p : details.getDetailValues()) {
+            Log.i(TAG, "addPreferenceListener: pref " + p.mKey + p.mValue);
+            Preference pref = findPreference(p.mKey);
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener(listener);
+            } else {
+                Log.w(TAG, "addPreferenceListener: pref not found");
+            }
+        }
+    }
+
+    Preference.OnPreferenceChangeListener changeBasicPreferenceListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+
+            Log.i(TAG, "Changing preference value:" + newValue);
+            if (preference instanceof CheckBoxPreference) {
+                mCallbacks.getAccount().getBasicDetails().setDetailString(preference.getKey(), newValue.toString());
+            } else {
+                if (preference instanceof PasswordPreference) {
+                    String tmp = "";
+                    for (int i = 0; i < ((String) newValue).length(); ++i) {
+                        tmp += "*";
+                    }
+                    if(mCallbacks.getAccount().isSip())
+                        mCallbacks.getAccount().getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString());
+                    preference.setSummary(tmp);
+                } else if(preference.getKey().contentEquals(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME)) {
+					if(mCallbacks.getAccount().isSip()){
+						mCallbacks.getAccount().getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString());
+					}
+                    preference.setSummary((CharSequence) newValue);
+                } else {
+                    preference.setSummary((CharSequence) newValue);
+                }
+                
+                mCallbacks.getAccount().getBasicDetails().setDetailString(preference.getKey(), newValue.toString());
+            }
+            mCallbacks.getAccount().notifyObservers();
+            return true;
+        }
+    };
+
+}
diff --git a/ring-android/src/cx/ring/fragments/HistoryFragment.java b/ring-android/src/cx/ring/fragments/HistoryFragment.java
new file mode 100644
index 0000000..7a04f0a
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/HistoryFragment.java
@@ -0,0 +1,324 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.view.*;
+import cx.ring.R;
+import cx.ring.adapters.ContactPictureTask;
+import cx.ring.client.DetailHistoryActivity;
+import cx.ring.history.HistoryManager;
+import cx.ring.loaders.HistoryLoader;
+import cx.ring.loaders.LoaderConstants;
+import cx.ring.history.HistoryEntry;
+import cx.ring.service.ISipService;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+public class HistoryFragment extends ListFragment implements LoaderManager.LoaderCallbacks<ArrayList<HistoryEntry>> {
+
+    private static final String TAG = HistoryFragment.class.getSimpleName();
+
+    HistoryAdapter mAdapter;
+    private Callbacks mCallbacks = sDummyCallbacks;
+    HistoryManager mHistoryManager;
+
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+        @Override
+        public void onCallHistory(HistoryEntry to) {
+        }
+
+        @Override
+        public ISipService getService() {
+            Log.i(TAG, "Dummy");
+            return null;
+        }
+
+    };
+
+    public static String ARGS = "Bundle.args";
+
+    public interface Callbacks {
+        public void onCallHistory(HistoryEntry to);
+
+        public ISipService getService();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        Log.i(TAG, "Attaching HISTORY");
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.history, menu);
+        mHistoryManager = new HistoryManager(getActivity());
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_clear_history:
+                // TODO clean Database!
+                mHistoryManager.clearDB();
+                getLoaderManager().restartLoader(LoaderConstants.HISTORY_LOADER, null, this);
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAdapter = new HistoryAdapter(getActivity(), new ArrayList<HistoryEntry>());
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_history, parent, false);
+
+        return inflatedView;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+
+        super.onActivityCreated(savedInstanceState);
+        getListView().setAdapter(mAdapter);
+
+        getListView().setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+
+                Bundle b = new Bundle();
+                b.putParcelable("entry", mAdapter.getItem(pos));
+                Intent toStart = new Intent(getActivity(), DetailHistoryActivity.class).putExtra(HistoryFragment.ARGS, b);
+                startActivity(toStart);
+
+            }
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        Log.w(TAG, "onStart");
+        getLoaderManager().restartLoader(LoaderConstants.HISTORY_LOADER, null, this);
+    }
+
+    public void makeNewCall(int position) {
+        mCallbacks.onCallHistory(mAdapter.getItem(position));
+    }
+
+    public class HistoryAdapter extends BaseAdapter implements ListAdapter {
+
+        Context mContext;
+        ArrayList<HistoryEntry> dataset;
+        private ExecutorService infos_fetcher = Executors.newCachedThreadPool();
+
+        public HistoryAdapter(Context activity, ArrayList<HistoryEntry> history) {
+            mContext = activity;
+            dataset = history;
+        }
+
+        @Override
+        public View getView(final int pos, View convertView, ViewGroup arg2) {
+
+            HistoryView entryView;
+
+            if (convertView == null) {
+                // Get a new instance of the row layout view
+                LayoutInflater inflater = LayoutInflater.from(mContext);
+                convertView = inflater.inflate(R.layout.item_history, null);
+
+                // Hold the view objects in an object
+                // so they don't need to be re-fetched
+                entryView = new HistoryView();
+                entryView.photo = (ImageButton) convertView.findViewById(R.id.photo);
+                entryView.displayName = (TextView) convertView.findViewById(R.id.display_name);
+                entryView.date = (TextView) convertView.findViewById(R.id.date_start);
+                entryView.incoming = (TextView) convertView.findViewById(R.id.incomings);
+                entryView.outgoing = (TextView) convertView.findViewById(R.id.outgoings);
+                entryView.replay = (Button) convertView.findViewById(R.id.replay);
+                convertView.setTag(entryView);
+            } else {
+                entryView = (HistoryView) convertView.getTag();
+            }
+
+            // Transfer the stock data from the data object
+            // to the view objects
+
+            // SipCall call = (SipCall) mCallList.values().toArray()[position];
+            entryView.displayName.setText(dataset.get(pos).getContact().getmDisplayName());
+            infos_fetcher.execute(new ContactPictureTask(mContext, entryView.photo, dataset.get(pos).getContact()));
+
+            entryView.incoming.setText(getString(R.string.hist_in_calls, dataset.get(pos).getIncoming_sum()));
+            entryView.outgoing.setText(getString(R.string.hist_out_calls, dataset.get(pos).getOutgoing_sum()));
+
+            /*if (dataset.get(pos).getCalls().lastEntry().getValue().getRecordPath().length() > 0) {
+                entryView.replay.setVisibility(View.VISIBLE);
+                entryView.replay.setTag(R.id.replay, true);
+                entryView.replay.setOnClickListener(new OnClickListener() {
+
+                    @Override
+                    public void onClick(View v) {
+                        try {
+                            if ((Boolean) v.getTag(R.id.replay)) {
+                                mCallbacks.getService().startRecordedFilePlayback(dataset.get(pos).getCalls().lastEntry().getValue().getRecordPath());
+                                v.setTag(R.id.replay, false);
+                                ((Button) v).setText(getString(R.string.hist_replay_button_stop));
+                            } else {
+                                mCallbacks.getService().stopRecordedFilePlayback(dataset.get(pos).getCalls().lastEntry().getValue().getRecordPath());
+                                v.setTag(R.id.replay, true);
+                                ((Button) v).setText(getString(R.string.hist_replay_button));
+                            }
+                        } catch (RemoteException e) {
+                            // TODO Auto-generated catch block
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }*/
+
+            /*entryView.date.setText(dataset.get(pos).getCalls().lastEntry().getValue().getDate());*/
+            entryView.photo.setOnClickListener(new OnClickListener() {
+
+                @Override
+                public void onClick(View v) {
+                    makeNewCall(pos);
+
+                }
+            });
+
+            return convertView;
+
+        }
+
+        /**
+         * ******************
+         * ViewHolder Pattern
+         * *******************
+         */
+        public class HistoryView {
+            public ImageButton photo;
+            protected TextView displayName;
+            protected TextView date;
+            private Button replay;
+            private TextView outgoing;
+            private TextView incoming;
+        }
+
+        @Override
+        public int getCount() {
+
+            return dataset.size();
+        }
+
+        @Override
+        public HistoryEntry getItem(int pos) {
+            return dataset.get(pos);
+        }
+
+        @Override
+        public long getItemId(int arg0) {
+            return 0;
+        }
+
+        public void clear() {
+            dataset.clear();
+
+        }
+
+        public void addAll(ArrayList<HistoryEntry> history) {
+            dataset.addAll(history);
+        }
+
+    }
+
+    @Override
+    public AsyncTaskLoader<ArrayList<HistoryEntry>> onCreateLoader(int arg0, Bundle arg1) {
+        HistoryLoader loader = new HistoryLoader(getActivity());
+        loader.forceLoad();
+        return loader;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<ArrayList<HistoryEntry>> arrayListLoader, ArrayList<HistoryEntry> historyEntries) {
+        mAdapter.clear();
+        mAdapter.addAll(historyEntries);
+        mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<ArrayList<HistoryEntry>> arrayListLoader) {
+
+    }
+
+
+}
diff --git a/ring-android/src/cx/ring/fragments/HomeFragment.java b/ring-android/src/cx/ring/fragments/HomeFragment.java
new file mode 100644
index 0000000..e3066c9
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/HomeFragment.java
@@ -0,0 +1,122 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import android.support.v4.app.Fragment;
+import cx.ring.R;
+import cx.ring.adapters.SectionsPagerAdapter;
+import cx.ring.views.PagerSlidingTabStrip;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.view.ViewPager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class HomeFragment extends Fragment {
+    static final String TAG = HomeFragment.class.getSimpleName();
+
+    /**
+     * The {@link ViewPager} that will host the section contents.
+     */
+    ViewPager mViewPager;
+    SectionsPagerAdapter mSectionsPagerAdapter;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getActivity().getActionBar().setTitle(R.string.menu_item_home);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    @Override
+    public void onCreate(Bundle savedBundle) {
+        super.onCreate(savedBundle);
+        mSectionsPagerAdapter = new SectionsPagerAdapter(getActivity(), getChildFragmentManager());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_home, container, false);
+
+        // Set up the ViewPager with the sections adapter.
+        mViewPager = (ViewPager) rootView.findViewById(R.id.pager);
+        mViewPager.setPageTransformer(true, new ZoomOutPageTransformer(0.7f));
+
+        mViewPager.setOffscreenPageLimit(2);
+        mViewPager.setAdapter(mSectionsPagerAdapter);
+        mViewPager.setCurrentItem(1);
+
+        final PagerSlidingTabStrip strip = PagerSlidingTabStrip.class.cast(rootView.findViewById(R.id.pts_main));
+
+        strip.setViewPager(mViewPager);
+
+        return rootView;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
+        private static final float MIN_ALPHA = .6f;
+
+        public ZoomOutPageTransformer(float scalingStart) {
+            super();
+        }
+
+        @Override
+        public void transformPage(View page, float position) {
+            final float normalizedposition = Math.abs(Math.abs(position) - 1);
+            page.setAlpha(MIN_ALPHA + (1.f - MIN_ALPHA) * normalizedposition);
+        }
+    }
+
+    public SectionsPagerAdapter getSectionsPagerAdapter() {
+        return mSectionsPagerAdapter;
+    }
+
+    public ViewPager getViewPager() {
+        return mViewPager;
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/fragments/IMFragment.java b/ring-android/src/cx/ring/fragments/IMFragment.java
new file mode 100644
index 0000000..0769d63
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/IMFragment.java
@@ -0,0 +1,193 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import android.widget.*;
+import cx.ring.R;
+import cx.ring.adapters.DiscussArrayAdapter;
+import cx.ring.model.Conference;
+import cx.ring.model.SipMessage;
+import cx.ring.service.ISipService;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView.OnEditorActionListener;
+
+public class IMFragment extends CallableWrapperFragment {
+    static final String TAG = IMFragment.class.getSimpleName();
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+
+    DiscussArrayAdapter mAdapter;
+    ListView list;
+
+    private EditText sendTextField;
+
+    @Override
+    public void onCreate(Bundle savedBundle) {
+        super.onCreate(savedBundle);
+
+        mAdapter = new DiscussArrayAdapter(getActivity(), getArguments());
+
+    }
+
+    @Override
+    public void incomingText(Conference updated, String ID, String from, String msg) {
+        mCallbacks.updateDisplayedConference(updated);
+        if(updated.equals(mCallbacks.getDisplayedConference())){
+            SipMessage sipMsg = new SipMessage(true, msg);
+            putMessage(sipMsg);
+        }
+
+    }
+
+
+    /**
+     * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
+     */
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+        @Override
+        public Conference getDisplayedConference() {
+            return null;
+        }
+
+        @Override
+        public boolean sendIM(SipMessage msg) {
+            return false;
+        }
+
+        @Override
+        public void updateDisplayedConference(Conference c) {
+
+        }
+
+    };
+
+    /**
+     * The Activity calling this fragment has to implement this interface
+     */
+    public interface Callbacks {
+        public ISipService getService();
+
+        public Conference getDisplayedConference();
+
+        public boolean sendIM(SipMessage msg);
+
+        public void updateDisplayedConference(Conference c);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_imessaging, container, false);
+
+        list = (ListView) rootView.findViewById(R.id.message_list);
+        list.setAdapter(mAdapter);
+
+        sendTextField = (EditText) rootView.findViewById(R.id.send_im_edittext);
+
+        sendTextField.setOnEditorActionListener(new OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+
+                if (actionId == EditorInfo.IME_ACTION_SEND) {
+                    sendMessage();
+                }
+                return true;
+            }
+        });
+
+        rootView.findViewById(R.id.send_im_button).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                sendMessage();
+            }
+        });
+
+
+        return rootView;
+    }
+
+    private void sendMessage() {
+        if (sendTextField.getText().toString().length() > 0) {
+            SipMessage toSend = new SipMessage(false, sendTextField.getText().toString());
+            if (mCallbacks.sendIM(toSend)) {
+                putMessage(toSend);
+                sendTextField.setText("");
+            } else {
+                Toast.makeText(getActivity(), "Error sending message", Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    public void putMessage(SipMessage msg) {
+        mAdapter.add(msg);
+        Log.i(TAG, "Messages" + mAdapter.getCount());
+    }
+}
diff --git a/ring-android/src/cx/ring/fragments/MenuFragment.java b/ring-android/src/cx/ring/fragments/MenuFragment.java
new file mode 100644
index 0000000..d7653d8
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/MenuFragment.java
@@ -0,0 +1,237 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.fragments;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.*;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import cx.ring.R;
+import cx.ring.adapters.AccountSelectionAdapter;
+import cx.ring.adapters.ContactPictureTask;
+import cx.ring.loaders.AccountsLoader;
+import cx.ring.loaders.LoaderConstants;
+import cx.ring.model.account.Account;
+import cx.ring.model.CallContact;
+import cx.ring.service.ISipService;
+
+import java.util.ArrayList;
+
+public class MenuFragment extends AccountWrapperFragment implements LoaderManager.LoaderCallbacks<Bundle> {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = MenuFragment.class.getSimpleName();
+
+    AccountSelectionAdapter mAccountAdapter;
+    private Spinner spinnerAccounts;
+    private Callbacks mCallbacks = sDummyCallbacks;
+
+    private ListView sections;
+
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+        @Override
+        public void onSectionSelected(int pos) {
+
+        }
+    };
+
+    public Account retrieveAccountById(String accountID) {
+        Account toReturn;
+        toReturn = mAccountAdapter.getAccount(accountID);
+
+        if(toReturn == null || !toReturn.isRegistered())
+            return getSelectedAccount();
+
+        return toReturn;
+    }
+
+    public interface Callbacks {
+
+        public ISipService getService();
+
+        public void onSectionSelected(int pos);
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    public void onResume() {
+        super.onResume();
+
+        Log.i(TAG, "Resuming");
+        getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this);
+
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this);
+    }
+
+
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+        View inflatedView = inflater.inflate(R.layout.frag_menu, parent, false);
+
+        ArrayAdapter<String> paramAdapter = new ArrayAdapter<String>(getActivity(), R.layout.item_menu, getResources().getStringArray(
+                R.array.menu_items_param));
+        sections = (ListView) inflatedView.findViewById(R.id.listView);
+        sections.setAdapter(paramAdapter);
+        backToHome();
+        sections.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View selected, int pos, long arg3) {
+                mCallbacks.onSectionSelected(pos);
+            }
+        });
+
+        spinnerAccounts = (Spinner) inflatedView.findViewById(R.id.account_selection);
+        mAccountAdapter = new AccountSelectionAdapter(getActivity(), new ArrayList<Account>());
+        spinnerAccounts.setAdapter(mAccountAdapter);
+        spinnerAccounts.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+            @Override
+            public void onItemSelected(AdapterView<?> arg0, View view, int pos, long arg3) {
+                mAccountAdapter.setSelectedAccount(pos);
+                view.findViewById(R.id.account_selected).setVisibility(View.GONE);
+                try {
+                    mCallbacks.getService().setAccountOrder(mAccountAdapter.getAccountOrder());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> arg0) {
+                mAccountAdapter.setSelectedAccount(-1);
+            }
+        });
+
+        CallContact user = CallContact.ContactBuilder.buildUserContact(getActivity().getContentResolver());
+        new ContactPictureTask(getActivity(), (ImageView) inflatedView.findViewById(R.id.user_photo), user).run();
+
+        ((TextView) inflatedView.findViewById(R.id.user_name)).setText(user.getmDisplayName());
+
+        return inflatedView;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    public Account getSelectedAccount() {
+        return mAccountAdapter.getSelectedAccount();
+    }
+
+    public void updateAllAccounts() {
+        if (getActivity() != null)
+            getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this);
+    }
+
+    @Override
+    public void accountsChanged() {
+        updateAllAccounts();
+
+    }
+
+    @Override
+    public void accountStateChanged(String accoundID, String state, int code) {
+        if (mAccountAdapter != null)
+            mAccountAdapter.updateAccount(accoundID, state, code);
+    }
+
+    @Override
+    public AsyncTaskLoader<Bundle> onCreateLoader(int arg0, Bundle arg1) {
+        AccountsLoader l = new AccountsLoader(getActivity(), mCallbacks.getService());
+        l.forceLoad();
+        return l;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
+        mAccountAdapter.removeAll();
+        ArrayList<Account> accounts = data.getParcelableArrayList(AccountsLoader.ACCOUNTS);
+        mAccountAdapter.addAll(accounts);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Bundle> loader) {
+
+    }
+
+    public void backToHome() {
+        sections.setItemChecked(0, true);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/fragments/NestedSettingsFragment.java b/ring-android/src/cx/ring/fragments/NestedSettingsFragment.java
new file mode 100644
index 0000000..81a66ea
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/NestedSettingsFragment.java
@@ -0,0 +1,191 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import android.content.Intent;
+import android.os.RemoteException;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import cx.ring.model.account.CredentialsManager;
+import cx.ring.model.account.SRTPManager;
+import cx.ring.model.account.TLSManager;
+import cx.ring.model.account.Account;
+import cx.ring.service.ISipService;
+
+import java.util.ArrayList;
+
+public class NestedSettingsFragment extends PreferenceFragment {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = AdvancedAccountFragment.class.getSimpleName();
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+
+    CredentialsManager mCredsManager;
+    SRTPManager mSrtpManager;
+    TLSManager mTlsManager;
+
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public Account getAccount() {
+            return null;
+        }
+
+        @Override
+        public ISipService getService() {
+            return null;
+        }
+
+    };
+
+    public String[] getTlsMethods() {
+        ArrayList<String> methods = null;
+        try {
+            methods = (ArrayList<String>) mCallbacks.getService().getTlsSupportedMethods();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        String[] results = new String[methods.size()];
+        methods.toArray(results);
+        return results;
+    }
+
+    public boolean checkCertificate(String crt) {
+        try {
+             return mCallbacks.getService().checkCertificateValidity(crt);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public boolean findRSAKey(String pemPath) {
+        try {
+            return mCallbacks.getService().checkForPrivateKey(pemPath);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public interface Callbacks {
+
+        public Account getAccount();
+
+        public ISipService getService();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setHasOptionsMenu(true);
+
+        // Load the preferences from an XML resource
+        switch (getArguments().getInt("MODE")) {
+            case 0: // Credentials
+                addPreferencesFromResource(cx.ring.R.xml.account_credentials);
+                mCredsManager = new CredentialsManager();
+                mCredsManager.onCreate(getActivity(), getPreferenceScreen(), mCallbacks.getAccount());
+                mCredsManager.reloadCredentials();
+                mCredsManager.setAddCredentialListener();
+                break;
+            case 1: // SRTP
+                mSrtpManager = new SRTPManager();
+                if (mCallbacks.getAccount().hasSDESEnabled()) { // SDES
+                    addPreferencesFromResource(cx.ring.R.xml.account_sdes);
+                    mSrtpManager.onCreate(getPreferenceScreen(), mCallbacks.getAccount());
+                    mSrtpManager.setSDESListener();
+                } else { // ZRTP
+                    addPreferencesFromResource(cx.ring.R.xml.account_zrtp);
+                    mSrtpManager.onCreate(getPreferenceScreen(), mCallbacks.getAccount());
+                    mSrtpManager.setZRTPListener();
+                }
+                break;
+            case 2:
+                addPreferencesFromResource(cx.ring.R.xml.account_tls);
+                mTlsManager = new TLSManager();
+                mTlsManager.onCreate(this, getPreferenceScreen(), mCallbacks.getAccount());
+                mTlsManager.setTLSListener();
+                break;
+        }
+
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        view.setBackgroundColor(getResources().getColor(android.R.color.white));
+        return view;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (mTlsManager != null) {
+            mTlsManager.onActivityResult(requestCode, resultCode, data);
+        }
+
+    }
+
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/fragments/SecurityAccountFragment.java b/ring-android/src/cx/ring/fragments/SecurityAccountFragment.java
new file mode 100644
index 0000000..01042e6
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/SecurityAccountFragment.java
@@ -0,0 +1,199 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import java.util.Locale;
+
+import cx.ring.R;
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import cx.ring.model.account.AccountDetail;
+import cx.ring.model.account.AccountDetailSrtp;
+import cx.ring.model.account.Account;
+
+public class SecurityAccountFragment extends PreferenceFragment {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = SecurityAccountFragment.class.getSimpleName();
+
+    private Callbacks mCallbacks = sDummyCallbacks;
+    private static Callbacks sDummyCallbacks = new Callbacks() {
+
+        @Override
+        public Account getAccount() {
+            return null;
+        }
+
+        @Override
+        public void displayCredentialsScreen() {
+        }
+
+        @Override
+        public void displaySRTPScreen() {
+        }
+
+        @Override
+        public void displayTLSScreen() {
+        }
+
+    };
+
+    public interface Callbacks {
+
+        public Account getAccount();
+
+        public void displayCredentialsScreen();
+
+        public void displaySRTPScreen();
+
+        public void displayTLSScreen();
+
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCallbacks = sDummyCallbacks;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if(mCallbacks.getAccount().getTlsDetails().getDetailBoolean("TLS.enable")){
+            findPreference("TLS.details").setSummary(getString(R.string.account_tls_enabled_label));
+        } else {
+            findPreference("TLS.details").setSummary(getString(R.string.account_tls_disabled_label));
+        }
+
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.account_security_prefs);
+        updateSummaries();
+        findPreference("Credential.count").setOnPreferenceClickListener(new OnPreferenceClickListener() {
+
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                mCallbacks.displayCredentialsScreen();
+                return false;
+            }
+        });
+
+        setSrtpPreferenceDetails(mCallbacks.getAccount().getSrtpDetails());
+        addPreferenceListener(mCallbacks.getAccount().getSrtpDetails(), changeSrtpModeListener);
+
+        findPreference("TLS.details").setOnPreferenceClickListener(new OnPreferenceClickListener() {
+
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                mCallbacks.displayTLSScreen();
+                return false;
+            }
+        });
+
+    }
+
+    public void updateSummaries() {
+        findPreference("Credential.count").setSummary("" + mCallbacks.getAccount().getCredentials().size());
+        if(mCallbacks.getAccount().getTlsDetails().getDetailBoolean("TLS.enable")){
+            findPreference("TLS.details").setSummary(getString(R.string.account_tls_enabled_label));
+        } else {
+            findPreference("TLS.details").setSummary(getString(R.string.account_tls_disabled_label));
+        }
+    }
+
+    private void setSrtpPreferenceDetails(AccountDetailSrtp details) {
+
+        if (details.getDetailBoolean(AccountDetailSrtp.CONFIG_SRTP_ENABLE)) {
+            findPreference(AccountDetailSrtp.CONFIG_SRTP_ENABLE).setSummary(
+                    details.getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE).toUpperCase(Locale.getDefault()));
+
+        } else {
+            findPreference(AccountDetailSrtp.CONFIG_SRTP_ENABLE).setSummary(getResources().getString(R.string.account_srtp_deactivated));
+
+        }
+
+        findPreference("SRTP.details").setEnabled(details.getDetailBoolean(AccountDetailSrtp.CONFIG_SRTP_ENABLE));
+    }
+
+    private void addPreferenceListener(AccountDetail details, OnPreferenceChangeListener listener) {
+
+        findPreference(AccountDetailSrtp.CONFIG_SRTP_ENABLE).setOnPreferenceChangeListener(changeSrtpModeListener);
+        findPreference("SRTP.details").setOnPreferenceClickListener(new OnPreferenceClickListener() {
+
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                mCallbacks.displaySRTPScreen();
+                return false;
+            }
+        });
+
+    }
+
+    Preference.OnPreferenceChangeListener changeSrtpModeListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+
+            if (((String) newValue).contentEquals("NONE")) {
+                mCallbacks.getAccount().getSrtpDetails().setDetailString(AccountDetailSrtp.CONFIG_SRTP_ENABLE, AccountDetail.FALSE_STR);
+                preference.setSummary(getResources().getString(R.string.account_srtp_deactivated));
+            } else {
+                mCallbacks.getAccount().getSrtpDetails().setDetailString(AccountDetailSrtp.CONFIG_SRTP_ENABLE, AccountDetail.TRUE_STR);
+                mCallbacks.getAccount().getSrtpDetails()
+                        .setDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE, ((String) newValue).toLowerCase(Locale.getDefault()));
+                preference.setSummary(((String) newValue));
+            }
+            findPreference("SRTP.details").setEnabled(!((String) newValue).contentEquals("NONE"));
+            mCallbacks.getAccount().notifyObservers();
+            return true;
+        }
+    };
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/fragments/TransferDFragment.java b/ring-android/src/cx/ring/fragments/TransferDFragment.java
new file mode 100644
index 0000000..9d51c78
--- /dev/null
+++ b/ring-android/src/cx/ring/fragments/TransferDFragment.java
@@ -0,0 +1,303 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.fragments;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Dialog;
+import android.support.v4.app.DialogFragment;
+import cx.ring.R;
+import cx.ring.loaders.ContactsLoader;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnShowListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.location.Address;
+import android.location.Geocoder;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class TransferDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Bundle> {
+    public static final int RESULT_TRANSFER_CONF = Activity.RESULT_FIRST_USER + 1;
+    public static final int RESULT_TRANSFER_NUMBER = Activity.RESULT_FIRST_USER + 2;
+
+    private AutoCompleteTextView mEditText;
+    private AutoCompleteAdapter autoCompleteAdapter;
+    SimpleCallListAdapter mAdapter;
+
+    /**
+     * Create a new instance of CallActionsDFragment
+     */
+    static TransferDFragment newInstance() {
+        TransferDFragment f = new TransferDFragment();
+        return f;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Pick a style based on the num.
+        int style = DialogFragment.STYLE_NORMAL, theme = 0;
+        setStyle(style, theme);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_transfer, null);
+
+        ArrayList<Conference> calls = getArguments().getParcelableArrayList("calls");
+        final SipCall call_selected = getArguments().getParcelable("call_selected");
+
+        mAdapter = new SimpleCallListAdapter(getActivity(), calls);
+        ListView list = (ListView) rootView.findViewById(R.id.concurrent_calls);
+        list.setAdapter(mAdapter);
+        list.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
+
+                Intent in = new Intent();
+                in.putExtra("target", mAdapter.getItem(pos));
+                in.putExtra("transfer", call_selected);
+
+                getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_TRANSFER_CONF, in);
+                dismiss();
+            }
+        });
+        list.setEmptyView(rootView.findViewById(R.id.empty_view));
+
+        mEditText = (AutoCompleteTextView) rootView.findViewById(R.id.external_number);
+        mEditText.setAdapter(autoCompleteAdapter);
+
+        final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView)
+                .setTitle("Transfer " + call_selected.getmContact().getmDisplayName())
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+
+                    }
+                }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, new Intent());
+                        dismiss();
+                    }
+                }).create();
+
+        a.setOnShowListener(new OnShowListener() {
+
+            @Override
+            public void onShow(DialogInterface dialog) {
+                Button b = a.getButton(AlertDialog.BUTTON_POSITIVE);
+                b.setOnClickListener(new View.OnClickListener() {
+
+                    @Override
+                    public void onClick(View view) {
+                        if(mEditText.getText().length() == 0){
+                            Toast.makeText(getActivity(), "Enter a number to transfer this call", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Intent in = new Intent();
+                            in.putExtra("to_number", mEditText.getText().toString());
+                            in.putExtra("transfer", call_selected);
+                            getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_TRANSFER_NUMBER, in);
+                            dismiss();
+                        }
+                    }
+                });
+
+            }
+        });
+        return a;
+    }
+
+    @Override
+    public Loader<Bundle> onCreateLoader(int id, Bundle args) {
+        Uri baseUri;
+
+        if (args != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(args.getString("filter")));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+        ContactsLoader l = new ContactsLoader(getActivity(), baseUri);
+        l.forceLoad();
+        return l;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
+
+//        ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts");
+
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Bundle> loader) {
+        // Thi is called when the last Cursor provided to onLoadFinished
+        // mListAdapter.swapCursor(null);
+    }
+
+    private class AutoCompleteAdapter extends ArrayAdapter<Address> implements Filterable {
+
+        private LayoutInflater mInflater;
+        private Geocoder mGeocoder;
+//        private StringBuilder mSb = new StringBuilder();
+
+        public AutoCompleteAdapter(final Context context) {
+            super(context, -1);
+            mInflater = LayoutInflater.from(context);
+            mGeocoder = new Geocoder(context);
+        }
+
+        @Override
+        public View getView(final int position, final View convertView, final ViewGroup parent) {
+            final TextView tv;
+            if (convertView != null) {
+                tv = (TextView) convertView;
+            } else {
+                tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
+            }
+
+            return tv;
+        }
+
+        @Override
+        public Filter getFilter() {
+            Filter myFilter = new Filter() {
+                @Override
+                protected FilterResults performFiltering(final CharSequence constraint) {
+                    List<Address> addressList = null;
+                    if (constraint != null) {
+                        try {
+                            addressList = mGeocoder.getFromLocationName((String) constraint, 5);
+                        } catch (IOException e) {
+                        }
+                    }
+                    if (addressList == null) {
+                        addressList = new ArrayList<Address>();
+                    }
+
+                    final FilterResults filterResults = new FilterResults();
+                    filterResults.values = addressList;
+                    filterResults.count = addressList.size();
+
+                    return filterResults;
+                }
+
+                @SuppressWarnings("unchecked")
+                @Override
+                protected void publishResults(final CharSequence contraint, final FilterResults results) {
+                    clear();
+                    for (Address address : (List<Address>) results.values) {
+                        add(address);
+                    }
+                    if (results.count > 0) {
+                        notifyDataSetChanged();
+                    } else {
+                        notifyDataSetInvalidated();
+                    }
+                }
+
+                @Override
+                public CharSequence convertResultToString(final Object resultValue) {
+                    return resultValue == null ? "" : ((Address) resultValue).getAddressLine(0);
+                }
+            };
+            return myFilter;
+        }
+    }
+
+    private class SimpleCallListAdapter extends BaseAdapter {
+
+        private LayoutInflater mInflater;
+        ArrayList<Conference> calls;
+
+        public SimpleCallListAdapter(final Context context, ArrayList<Conference> calls2) {
+            super();
+            mInflater = LayoutInflater.from(context);
+            calls = calls2;
+        }
+
+        @Override
+        public View getView(final int position, final View convertView, final ViewGroup parent) {
+            final TextView tv;
+            if (convertView != null) {
+                tv = (TextView) convertView;
+            } else {
+                tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
+            }
+
+            tv.setText(calls.get(position).getParticipants().get(0).getmContact().getmDisplayName());
+            return tv;
+        }
+
+        @Override
+        public int getCount() {
+            return calls.size();
+        }
+
+        @Override
+        public Conference getItem(int pos) {
+            return calls.get(pos);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/history/DatabaseHelper.java b/ring-android/src/cx/ring/history/DatabaseHelper.java
new file mode 100644
index 0000000..a623e98
--- /dev/null
+++ b/ring-android/src/cx/ring/history/DatabaseHelper.java
@@ -0,0 +1,114 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+
+package cx.ring.history;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
+import com.j256.ormlite.dao.Dao;
+import com.j256.ormlite.support.ConnectionSource;
+import com.j256.ormlite.table.TableUtils;
+
+import java.sql.SQLException;
+
+/**
+ * Database helper class used to manage the creation and upgrading of your database. This class also usually provides
+ * the DAOs used by the other classes.
+ */
+public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
+
+    // name of the database file for your application -- change to something appropriate for your app
+    private static final String DATABASE_NAME = "history.db";
+    // any time you make changes to your database objects, you may have to increase the database version
+    private static final int DATABASE_VERSION = 2;
+
+    // the DAO object we use to access the SimpleData table
+    private Dao<HistoryCall, Integer> historyDao = null;
+
+    public DatabaseHelper(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+
+    /**
+     * This is called when the database is first created. Usually you should call createTable statements here to create
+     * the tables that will store your data.
+     */
+    @Override
+    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
+        try {
+            Log.i(DatabaseHelper.class.getName(), "onCreate");
+            TableUtils.createTable(connectionSource, HistoryCall.class);
+        } catch (SQLException e) {
+            Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * This is called when your application is upgraded and it has a higher version number. This allows you to adjust
+     * the various data to match the new version number.
+     */
+    @Override
+    public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
+        try {
+            Log.i(DatabaseHelper.class.getName(), "onUpgrade");
+            TableUtils.dropTable(connectionSource, HistoryCall.class, true);
+            // after we drop the old databases, we create the new ones
+            onCreate(db, connectionSource);
+        } catch (SQLException e) {
+            Log.e(DatabaseHelper.class.getName(), "Can't drop databases", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
+     * value.
+     */
+    public Dao<HistoryCall, Integer> getHistoryDao() throws SQLException {
+        if (historyDao == null) {
+            historyDao = getDao(HistoryCall.class);
+        }
+        return historyDao;
+    }
+
+    /**
+     * Close the database connections and clear any cached DAOs.
+     */
+    @Override
+    public void close() {
+        super.close();
+        historyDao = null;
+    }
+}
diff --git a/ring-android/src/cx/ring/history/HistoryCall.java b/ring-android/src/cx/ring/history/HistoryCall.java
new file mode 100644
index 0000000..2e76729
--- /dev/null
+++ b/ring-android/src/cx/ring/history/HistoryCall.java
@@ -0,0 +1,192 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+
+package cx.ring.history;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.j256.ormlite.field.DatabaseField;
+import cx.ring.model.SipCall;
+
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class HistoryCall implements Parcelable {
+
+    @DatabaseField(index = true, columnName="TIMESTAMP_START")
+    long call_start;
+    @DatabaseField
+    long call_end;
+    @DatabaseField
+    String number;
+    @DatabaseField
+    boolean missed;
+    @DatabaseField
+    int direction;
+    @DatabaseField
+    String recordPath;
+    @DatabaseField
+    String accountID;
+    @DatabaseField
+    long contactID;
+    @DatabaseField
+    String callID;
+
+    public String getAccountID() {
+        return accountID;
+    }
+
+    public long getContactID() {
+        return contactID;
+    }
+
+    public HistoryCall(SipCall call) {
+        call_start = call.getTimestampStart_();
+        call_end = call.getTimestampEnd_();
+        accountID = call.getAccount().getAccountID();
+        number = call.getmContact().getPhones().get(0).getNumber();
+        missed = call.isRinging() && call.isIncoming();
+        direction = call.getCallType();
+        recordPath = call.getRecordPath();
+        contactID = call.getmContact().getId();
+        callID = call.getCallId();
+    }
+
+    /* Needed by ORMLite */
+    public HistoryCall() {
+    }
+
+    public String getDirection() {
+        switch (direction) {
+            case SipCall.direction.CALL_TYPE_INCOMING:
+                return "CALL_TYPE_INCOMING";
+            case SipCall.direction.CALL_TYPE_OUTGOING:
+                return "CALL_TYPE_OUTGOING";
+            default:
+                return "CALL_TYPE_UNDETERMINED";
+        }
+    }
+
+    public String getDate() {
+        return HistoryTimeModel.timeToHistoryConst(call_start);
+    }
+
+    public String getStartString(String format) {
+        Timestamp stamp = new Timestamp(call_start * 1000); // in milliseconds
+        Date date = new Date(stamp.getTime());
+        SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
+        sdf.setTimeZone(TimeZone.getDefault());
+        return sdf.format(date);
+
+    }
+
+    public String getDurationString() {
+
+        long duration = call_end - call_start;
+        if (duration < 60)
+            return String.format(Locale.getDefault(), "%02d secs", duration);
+
+        if (duration < 3600)
+            return String.format(Locale.getDefault(), "%02d mins %02d secs", (duration % 3600) / 60, (duration % 60));
+
+        return String.format(Locale.getDefault(), "%d h %02d mins %02d secs", duration / 3600, (duration % 3600) / 60, (duration % 60));
+
+    }
+
+    public long getDuration() {
+        return call_end - call_start;
+    }
+
+    public String getRecordPath() {
+        return recordPath;
+    }
+
+    public String getNumber() {
+        return number;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(call_start);
+        dest.writeLong(call_end);
+        dest.writeString(accountID);
+        dest.writeString(number);
+        dest.writeByte((byte) (missed ? 1 : 0));
+        dest.writeInt(direction);
+        dest.writeString(recordPath);
+        dest.writeLong(contactID);
+        dest.writeString(callID);
+    }
+
+    public static final Parcelable.Creator<HistoryCall> CREATOR = new Parcelable.Creator<HistoryCall>() {
+        public HistoryCall createFromParcel(Parcel in) {
+            return new HistoryCall(in);
+        }
+
+        public HistoryCall[] newArray(int size) {
+            return new HistoryCall[size];
+        }
+    };
+
+    private HistoryCall(Parcel in) {
+        call_start = in.readLong();
+        call_end = in.readLong();
+        accountID = in.readString();
+        number = in.readString();
+        missed = in.readByte() == 1;
+        direction = in.readInt();
+        recordPath = in.readString();
+        contactID = in.readLong();
+        callID = in.readString();
+    }
+
+    public boolean hasRecord() {
+        return recordPath.length() > 0;
+    }
+
+    public boolean isIncoming() {
+        return direction == SipCall.direction.CALL_TYPE_INCOMING;
+    }
+
+    public boolean isMissed() {
+        return missed;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/history/HistoryEntry.java b/ring-android/src/cx/ring/history/HistoryEntry.java
new file mode 100644
index 0000000..385355c
--- /dev/null
+++ b/ring-android/src/cx/ring/history/HistoryEntry.java
@@ -0,0 +1,179 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.history;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import cx.ring.model.CallContact;
+
+import java.util.ArrayList;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+public class HistoryEntry implements Parcelable {
+
+    private CallContact contact;
+    private NavigableMap<Long, HistoryCall> calls;
+    private String accountID;
+    int missed_sum;
+    int outgoing_sum;
+    int incoming_sum;
+
+    public HistoryEntry(String account, CallContact c) {
+        contact = c;
+        calls = new TreeMap<Long, HistoryCall>();
+        accountID = account;
+        missed_sum = outgoing_sum = incoming_sum = 0;
+    }
+
+    public String getAccountID() {
+        return accountID;
+    }
+
+    public void setAccountID(String accountID) {
+        this.accountID = accountID;
+    }
+
+    public NavigableMap<Long, HistoryCall> getCalls() {
+        return calls;
+    }
+
+    public CallContact getContact() {
+        return contact;
+    }
+
+    public void setContact(CallContact contact) {
+        this.contact = contact;
+    }
+
+    /**
+     * Each call is associated with a contact.
+     * When adding a call to an HIstoryEntry, this methods also verifies if we can update
+     * the contact (if contact is Unknown, replace it)
+     *
+     * @param historyCall The call to put in this HistoryEntry
+     * @param linkedTo    The associated CallContact
+     */
+    public void addHistoryCall(HistoryCall historyCall, CallContact linkedTo) {
+        calls.put(historyCall.call_start, historyCall);
+        if (historyCall.isIncoming()) {
+            ++incoming_sum;
+        } else {
+            ++outgoing_sum;
+        }
+        if (historyCall.isMissed())
+            missed_sum++;
+
+        if (contact.isUnknown() && !linkedTo.isUnknown())
+            setContact(linkedTo);
+    }
+
+    public String getNumber() {
+        return calls.lastEntry().getValue().number;
+    }
+
+    public String getTotalDuration() {
+        int duration = 0;
+        ArrayList<HistoryCall> all_calls = new ArrayList<HistoryCall>(calls.values());
+        for (HistoryCall all_call : all_calls) {
+            duration += all_call.getDuration();
+        }
+
+        if (duration < 60)
+            return duration + "s";
+
+        return duration / 60 + "min";
+    }
+
+    public int getMissed_sum() {
+        return missed_sum;
+    }
+
+    public int getOutgoing_sum() {
+        return outgoing_sum;
+    }
+
+    public int getIncoming_sum() {
+        return incoming_sum;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+
+        dest.writeParcelable(contact, 0);
+
+        dest.writeList(new ArrayList<HistoryCall>(calls.values()));
+        dest.writeList(new ArrayList<Long>(calls.keySet()));
+
+        dest.writeString(accountID);
+        dest.writeInt(missed_sum);
+        dest.writeInt(outgoing_sum);
+        dest.writeInt(incoming_sum);
+
+    }
+
+    public static final Parcelable.Creator<HistoryEntry> CREATOR = new Parcelable.Creator<HistoryEntry>() {
+        public HistoryEntry createFromParcel(Parcel in) {
+            return new HistoryEntry(in);
+        }
+
+        public HistoryEntry[] newArray(int size) {
+            return new HistoryEntry[size];
+        }
+    };
+
+    private HistoryEntry(Parcel in) {
+        contact = in.readParcelable(CallContact.class.getClassLoader());
+
+        ArrayList<HistoryCall> values = new ArrayList<HistoryCall>();
+        in.readList(values, HistoryCall.class.getClassLoader());
+
+        ArrayList<Long> keys = new ArrayList<Long>();
+        in.readList(keys, Long.class.getClassLoader());
+
+        calls = new TreeMap<Long, HistoryCall>();
+        for (int i = 0; i < keys.size(); ++i) {
+            calls.put(keys.get(i), values.get(i));
+        }
+
+        accountID = in.readString();
+        missed_sum = in.readInt();
+        outgoing_sum = in.readInt();
+        incoming_sum = in.readInt();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/history/HistoryManager.java b/ring-android/src/cx/ring/history/HistoryManager.java
new file mode 100644
index 0000000..591cbf0
--- /dev/null
+++ b/ring-android/src/cx/ring/history/HistoryManager.java
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.history;
+
+import android.content.Context;
+import com.j256.ormlite.android.apptools.OpenHelperManager;
+import com.j256.ormlite.stmt.QueryBuilder;
+import com.j256.ormlite.table.TableUtils;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+
+import java.sql.SQLException;
+import java.util.List;
+
+public class HistoryManager {
+
+    private Context mContext;
+    private DatabaseHelper historyDBHelper = null;
+
+    public HistoryManager(Context context) {
+        mContext = context;
+        getHelper();
+    }
+
+    public boolean insertNewEntry(Conference toInsert){
+        for (SipCall call : toInsert.getParticipants()) {
+            call.setTimestampEnd_(System.currentTimeMillis());
+            HistoryCall persistent = new HistoryCall(call);
+            try {
+                getHelper().getHistoryDao().create(persistent);
+            } catch (SQLException e) {
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+    * Necessary when user hang up a call in a Conference
+    * The call creates an HistoryCall, but the conference still goes on
+    */
+    public boolean insertNewEntry(SipCall toInsert){
+        return true;
+    }
+
+    /**
+     * Retrieve helper for our DB
+     */
+    private DatabaseHelper getHelper() {
+        if (historyDBHelper == null) {
+            historyDBHelper = OpenHelperManager.getHelper(mContext, DatabaseHelper.class);
+        }
+        return historyDBHelper;
+    }
+
+    public List<HistoryCall> getAll() throws SQLException {
+
+        QueryBuilder<HistoryCall, Integer> qb = getHelper().getHistoryDao().queryBuilder();
+        qb.orderBy("TIMESTAMP_START", true);
+
+        return getHelper().getHistoryDao().query(qb.prepare());
+    }
+
+    public boolean clearDB() {
+        try {
+            TableUtils.clearTable(getHelper().getConnectionSource(), HistoryCall.class);
+        } catch (SQLException e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/ring-android/src/cx/ring/history/HistoryTimeModel.java b/ring-android/src/cx/ring/history/HistoryTimeModel.java
new file mode 100644
index 0000000..14a9035
--- /dev/null
+++ b/ring-android/src/cx/ring/history/HistoryTimeModel.java
@@ -0,0 +1,137 @@
+package cx.ring.history;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Locale;
+
+public class HistoryTimeModel {
+
+    static ArrayList<String> timeCategories;
+
+    public interface HistoryTimeCategoryModel {
+        String TODAY = "Today"; // 0
+        String YESTERDAY = "Yesterday"; // 1
+        String TWO_DAYS = getDate(2, "MM/dd");// 2
+        String THREE_DAYS = getDate(3, "MM/dd");// 3
+        String FOUR_DAYS = getDate(4, "MM/dd");// 4
+        String FIVE_DAYS = getDate(5, "MM/dd");// 5
+        String SIX_DAYS = getDate(6, "MM/dd");// 6
+        String LAST_WEEK = "Last week"; // 7
+        String TWO_WEEKS = "Two weeks ago"; // 8
+        String THREE_WEEKS = "Three weeks ago"; // 9
+        String LAST_MONTH = "Last month"; // 10
+        String TWO_MONTH = "Two months ago"; // 11
+        String THREE_MONTH = "Three months ago"; // 12
+        String FOUR_MONTH = "Four months ago"; // 13
+        String FIVE_MONTH = "Five months ago"; // 14
+        String SIX_MONTH = "Six months ago"; // 15
+        String SEVEN_MONTH = "Seven months ago"; // 16
+        String EIGHT_MONTH = "Eight months ago"; // 17
+        String NINE_MONTH = "Nine months ago"; // 18
+        String TEN_MONTH = "Ten months ago"; // 19
+        String ELEVEN_MONTH = "Eleven months ago"; // 20
+        String TWELVE_MONTH = "Twelve months ago"; // 21
+        String LAST_YEAR = "Last year"; // 22
+        String LONG_TIME_AGO = "Very long time ago"; // 23
+        String NEVER = "Never"; // 24
+    }
+
+    private static final String TAG = HistoryManager.class.getSimpleName();
+
+    static Calendar removeDays(int ago) {
+        Calendar cal = Calendar.getInstance(Locale.getDefault());
+        int currentDay = cal.get(Calendar.DAY_OF_MONTH);
+        // Set the date to 2 days ago
+        cal.set(Calendar.DAY_OF_MONTH, currentDay - ago);
+        return cal;
+    }
+
+    static String getDate(int ago, String format) {
+        Calendar cal = removeDays(ago);
+        SimpleDateFormat objFormatter = new SimpleDateFormat(format, Locale.CANADA);
+        objFormatter.setTimeZone(cal.getTimeZone());
+
+        String result = objFormatter.format(cal.getTime());
+        cal.clear();
+        return result;
+    }
+
+    public static String timeToHistoryConst(long time) {
+
+        if(timeCategories == null){
+            initializeCategories();
+        }
+
+        long time2 = time;
+        long currentTime = Calendar.getInstance(Locale.getDefault()).getTime().getTime() / 1000; // in seconds
+
+        if (time < 0)
+            return HistoryTimeCategoryModel.NEVER;
+
+        // Check if part if the current Nychthemeron
+        if (currentTime - time <= 3600 * 24) // The future case would be a bug, but it have to be handled anyway or it will appear in
+            // "very long time ago"
+            return HistoryTimeCategoryModel.TODAY;
+
+        time2 -= time % (3600 * 24); // Reset to midnight
+        currentTime -= currentTime % (3600 * 24); // Reset to midnight
+        // Check for last week
+        if (currentTime - (6) * 3600 * 24 < time2) {
+            for (int i = 1; i < 7; i++) {
+                if (currentTime - ((i) * 3600 * 24) == time2)
+                    return timeCategories.get(i); // Yesterday to Six_days_ago
+            }
+        }
+        // Check for last month
+        else if (currentTime - ((4) * 7 * 24 * 3600) < time2) {
+            for (int i = 1; i < 4; i++) {
+                if (currentTime - ((i + 1) * 7 * 24 * 3600) < time2)
+                    return timeCategories.get(i + timeCategories.indexOf(HistoryTimeCategoryModel.LAST_WEEK) - 1); // Last_week to Three_weeks_ago
+            }
+        }
+        // Check for last year
+        else if (currentTime - (12) * 30.4f * 24 * 3600 < time2) {
+            for (int i = 1; i < 12; i++) {
+                if (currentTime - (i + 1) * 30.4f * 24 * 3600 < time2) // Not exact, but faster
+                    return timeCategories.get(i + timeCategories.indexOf(HistoryTimeCategoryModel.LAST_MONTH) - 1);
+                // Last_month to Twelve_months ago
+            }
+        }
+        // if (QDate::currentDate().addYears(-1) >= date && QDate::currentDate().addYears(-2) < date)
+        else if (currentTime - 365 * 24 * 3600 < time2)
+            return HistoryTimeCategoryModel.LAST_YEAR;
+
+        // Every other senario
+        return HistoryTimeCategoryModel.LONG_TIME_AGO;
+    }
+
+    private static void initializeCategories() {
+        timeCategories = new ArrayList<String>();
+        timeCategories.add(HistoryTimeCategoryModel.TODAY);
+        timeCategories.add(HistoryTimeCategoryModel.YESTERDAY);
+        timeCategories.add(HistoryTimeCategoryModel.TWO_DAYS);
+        timeCategories.add(HistoryTimeCategoryModel.THREE_DAYS);
+        timeCategories.add(HistoryTimeCategoryModel.FOUR_DAYS);
+        timeCategories.add(HistoryTimeCategoryModel.FIVE_DAYS);
+        timeCategories.add(HistoryTimeCategoryModel.SIX_DAYS);
+        timeCategories.add(HistoryTimeCategoryModel.LAST_WEEK);
+        timeCategories.add(HistoryTimeCategoryModel.TWO_WEEKS);
+        timeCategories.add(HistoryTimeCategoryModel.THREE_WEEKS);
+        timeCategories.add(HistoryTimeCategoryModel.LAST_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.TWO_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.THREE_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.FOUR_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.FIVE_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.SIX_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.SEVEN_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.EIGHT_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.NINE_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.TEN_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.ELEVEN_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.TWELVE_MONTH);
+        timeCategories.add(HistoryTimeCategoryModel.LAST_YEAR);
+        timeCategories.add(HistoryTimeCategoryModel.LONG_TIME_AGO);
+        timeCategories.add(HistoryTimeCategoryModel.NEVER);
+    }
+}
diff --git a/ring-android/src/cx/ring/interfaces/AccountsInterface.java b/ring-android/src/cx/ring/interfaces/AccountsInterface.java
new file mode 100644
index 0000000..e441647
--- /dev/null
+++ b/ring-android/src/cx/ring/interfaces/AccountsInterface.java
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.interfaces;
+
+public interface AccountsInterface {
+    
+    public void accountsChanged();
+
+    public void accountStateChanged(String accoundID, String state, int code);
+
+
+}
diff --git a/ring-android/src/cx/ring/interfaces/CallInterface.java b/ring-android/src/cx/ring/interfaces/CallInterface.java
new file mode 100644
index 0000000..c1cd902
--- /dev/null
+++ b/ring-android/src/cx/ring/interfaces/CallInterface.java
@@ -0,0 +1,64 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.interfaces;
+
+
+import cx.ring.model.Conference;
+
+import java.util.HashMap;
+
+public interface CallInterface {
+
+    public void callStateChanged(Conference c, String callID, String state);
+
+    public void incomingText(Conference c, String ID, String from, String msg);
+
+    public void confCreated(Conference c, String id);
+
+    public void confRemoved(Conference c, String id);
+
+    public void confChanged(Conference c, String id, String state);
+
+    public void recordingChanged(Conference c, String callID, String filename);
+
+    public void secureZrtpOn(Conference c, String id);
+
+    public void secureZrtpOff(Conference c, String id);
+
+    public void displaySAS(Conference c, String securedCallID);
+
+    public void zrtpNegotiationFailed(Conference c, String securedCallID);
+
+    public void zrtpNotSupported(Conference c, String securedCallID);
+
+    public void rtcpReportReceived(Conference c, HashMap<String, Integer> stats);
+}
diff --git a/ring-android/src/cx/ring/loaders/AccountsLoader.java b/ring-android/src/cx/ring/loaders/AccountsLoader.java
new file mode 100644
index 0000000..8e038ff
--- /dev/null
+++ b/ring-android/src/cx/ring/loaders/AccountsLoader.java
@@ -0,0 +1,191 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.loaders;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.support.v4.content.AsyncTaskLoader;
+import cx.ring.model.account.Account;
+import cx.ring.service.ISipService;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class AccountsLoader extends AsyncTaskLoader<Bundle> {
+
+    private static final String TAG = AccountsLoader.class.getSimpleName();
+    public static final String ACCOUNTS = "accounts";
+    public static final String ACCOUNT_IP2IP = "IP2IP";
+    ISipService service;
+    Bundle mData;
+
+    public AccountsLoader(Context context, ISipService ref) {
+        super(context);
+        service = ref;
+    }
+
+    /****************************************************/
+    /** (1) A task that performs the asynchronous load **/
+    /****************************************************/
+
+    @SuppressWarnings("unchecked")
+    // Hashmap runtime cast
+    @Override
+    public Bundle loadInBackground() {
+
+        ArrayList<Account> accounts = new ArrayList<Account>();
+        Account IP2IP = null;
+
+        try {
+            ArrayList<String> accountIDs = (ArrayList<String>) service.getAccountList();
+            HashMap<String, String> details;
+            ArrayList<HashMap<String, String>> credentials;
+            for (String id : accountIDs) {
+
+                if (id.contentEquals(ACCOUNT_IP2IP)) {
+                    details = (HashMap<String, String>) service.getAccountDetails(id);
+                    IP2IP = new Account(ACCOUNT_IP2IP, details, new ArrayList<HashMap<String, String>>()); // Empty credentials
+                    continue;
+                }
+                details = (HashMap<String, String>) service.getAccountDetails(id);
+                credentials = (ArrayList<HashMap<String, String>>) service.getCredentials(id);
+                Account tmp = new Account(id, details, credentials);
+
+                accounts.add(tmp);
+
+                Log.i(TAG, "account:" + tmp.getAlias() + " " + tmp.isEnabled());
+
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        } catch (NullPointerException e1) {
+            Log.e(TAG, e1.toString());
+        }
+
+        Bundle result = new Bundle();
+        result.putParcelableArrayList(ACCOUNTS, accounts);
+        result.putParcelable(ACCOUNT_IP2IP, IP2IP);
+        return result;
+    }
+
+
+    /********************************************************/
+    /** (2) Deliver the results to the registered listener **/
+    /********************************************************/
+
+    @Override
+    public void deliverResult(Bundle data) {
+        if (isReset()) {
+            // The Loader has been reset; ignore the result and invalidate the data.
+            releaseResources(data);
+            return;
+        }
+
+        // Hold a reference to the old data so it doesn't get garbage collected.
+        // We must protect it until the new data has been delivered.
+        Bundle oldData = mData;
+        mData = data;
+
+        if (isStarted()) {
+            // If the Loader is in a started state, deliver the results to the
+            // client. The superclass method does this for us.
+            super.deliverResult(data);
+        }
+
+        // Invalidate the old data as we don't need it any more.
+        if (oldData != null && oldData != data) {
+            releaseResources(oldData);
+        }
+    }
+
+    /*********************************************************/
+    /** (3) Implement the Loader’s state-dependent behavior **/
+    /*********************************************************/
+
+    @Override
+    protected void onStartLoading() {
+        if (mData != null) {
+            // Deliver any previously loaded data immediately.
+            deliverResult(mData);
+        }
+
+        if (takeContentChanged() || mData == null) {
+            // When the observer detects a change, it should call onContentChanged()
+            // on the Loader, which will cause the next call to takeContentChanged()
+            // to return true. If this is ever the case (or if the current data is
+            // null), we force a new load.
+            forceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        // The Loader is in a stopped state, so we should attempt to cancel the
+        // current load (if there is one).
+        cancelLoad();
+
+        // Note that we leave the observer as is. Loaders in a stopped state
+        // should still monitor the data source for changes so that the Loader
+        // will know to force a new load if it is ever started again.
+    }
+
+    @Override
+    protected void onReset() {
+        // Ensure the loader has been stopped.
+        onStopLoading();
+
+        // At this point we can release the resources associated with 'mData'.
+        if (mData != null) {
+            releaseResources(mData);
+            mData = null;
+        }
+    }
+
+    @Override
+    public void onCanceled(Bundle data) {
+        // Attempt to cancel the current asynchronous load.
+        super.onCanceled(data);
+
+        // The load has been canceled, so we should release the resources
+        // associated with 'data'.
+        releaseResources(data);
+    }
+
+    private void releaseResources(Bundle data) {
+        // For a simple List, there is nothing to do. For something like a Cursor, we
+        // would close it in this method. All resources associated with the Loader
+        // should be released here.
+    }
+}
diff --git a/ring-android/src/cx/ring/loaders/ContactsLoader.java b/ring-android/src/cx/ring/loaders/ContactsLoader.java
new file mode 100644
index 0000000..3482d34
--- /dev/null
+++ b/ring-android/src/cx/ring/loaders/ContactsLoader.java
@@ -0,0 +1,113 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.loaders;
+
+import java.util.ArrayList;
+
+import cx.ring.model.CallContact;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+
+public class ContactsLoader extends AsyncTaskLoader<Bundle> {
+    
+//    private static final String TAG = ContactsLoader.class.getSimpleName();
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, Contacts.STARRED };
+    static final String[] CONTACTS_PHONES_PROJECTION = new String[] { Phone.NUMBER, Phone.TYPE };
+    static final String[] CONTACTS_SIP_PROJECTION = new String[] { SipAddress.SIP_ADDRESS, SipAddress.TYPE };
+
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))";
+    Uri baseUri;
+
+    public ContactsLoader(Context context, Uri u) {
+        super(context);
+        baseUri = u;
+    }
+
+    @Override
+    public Bundle loadInBackground() {
+        ArrayList<CallContact> contacts = new ArrayList<CallContact>();
+        ArrayList<CallContact> starred = new ArrayList<CallContact>();
+
+        Cursor result = getContext().getContentResolver().query(baseUri, CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+        int iID = result.getColumnIndex(Contacts._ID);
+        int iName = result.getColumnIndex(Contacts.DISPLAY_NAME);
+        int iPhoto = result.getColumnIndex(Contacts.PHOTO_ID);
+        int iStarred = result.getColumnIndex(Contacts.STARRED);
+        CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance();
+        
+        while (result.moveToNext()) {
+            builder.startNewContact(result.getLong(iID), result.getString(iName), result.getLong(iPhoto));
+            
+//            Cursor cPhones = getContext().getContentResolver().query(Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION,
+//                    Phone.CONTACT_ID + " =" + result.getLong(iID), null, null);
+
+//            while (cPhones.moveToNext()) {
+//                builder.addPhoneNumber(cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER)), cPhones.getInt(cPhones.getColumnIndex(Phone.TYPE)));
+////                Log.i(TAG,"Phone:"+cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER)));
+//            }
+//            cPhones.close();
+//
+//            Cursor cSip = getContext().getContentResolver().query(Phone.CONTENT_URI, CONTACTS_SIP_PROJECTION,
+//                    Phone.CONTACT_ID + "=" + result.getLong(iID), null, null);
+//
+//            while (cSip.moveToNext()) {
+//                builder.addSipNumber(cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS)), cSip.getInt(cSip.getColumnIndex(SipAddress.TYPE)));
+////                Log.i(TAG,"Phone:"+cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS)));
+//            }
+//            cSip.close();
+
+            contacts.add(builder.build());
+            if (result.getInt(iStarred) == 1) {
+                starred.add(builder.build());
+            }
+           
+        }        
+        
+        result.close();
+        Bundle toReturn = new Bundle();
+        
+       toReturn.putParcelableArrayList("Contacts", contacts);
+       toReturn.putParcelableArrayList("Starred", starred);
+
+        return toReturn;
+    }
+}
diff --git a/ring-android/src/cx/ring/loaders/HistoryLoader.java b/ring-android/src/cx/ring/loaders/HistoryLoader.java
new file mode 100644
index 0000000..5d878aa
--- /dev/null
+++ b/ring-android/src/cx/ring/loaders/HistoryLoader.java
@@ -0,0 +1,172 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.support.v4.content.AsyncTaskLoader;
+import cx.ring.history.HistoryCall;
+import cx.ring.history.HistoryEntry;
+import cx.ring.history.HistoryManager;
+import cx.ring.model.CallContact;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HistoryLoader extends AsyncTaskLoader<ArrayList<HistoryEntry>> {
+
+    private static final String TAG = HistoryLoader.class.getSimpleName();
+
+    private HistoryManager historyManager = null;
+
+    public HistoryLoader(Context context) {
+        super(context);
+        historyManager = new HistoryManager(context);
+    }
+
+    @SuppressWarnings("unchecked")
+    // Hashmap runtime cast
+    @Override
+    public ArrayList<HistoryEntry> loadInBackground() {
+
+        HashMap<String,HistoryEntry> historyEntries = new HashMap<String, HistoryEntry>();
+
+        try {
+            List<HistoryCall> list = historyManager.getAll();
+            CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance();
+            for (HistoryCall call : list) {
+                CallContact contact;
+                if (call.getContactID() == CallContact.DEFAULT_ID) {
+                    contact = CallContact.ContactBuilder.buildUnknownContact(call.getNumber());
+                } else {
+                    Cursor result = getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,
+                            ContactsContract.Contacts._ID + " = ?",
+                            new String[]{String.valueOf(call.getContactID())}, null);
+                    int iID = result.getColumnIndex(ContactsContract.Contacts._ID);
+                    int iName = result.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
+                    int iPhoto = result.getColumnIndex(ContactsContract.Contacts.PHOTO_ID);
+
+                    if (result.moveToFirst()) {
+                        builder.startNewContact(result.getLong(iID), result.getString(iName), result.getLong(iPhoto));
+                        builder.addPhoneNumber(call.getNumber(), 0);
+                        contact = builder.build();
+                    } else {
+                        contact = CallContact.ContactBuilder.buildUnknownContact(call.getNumber());
+                    }
+                    result.close();
+                }
+
+                if (historyEntries.containsKey(call.getNumber())) {
+                    // It's a direct match
+                    historyEntries.get(call.getNumber()).addHistoryCall(call, contact);
+                } else {
+                    // Maybe we can extract the extension @ account pattern
+                    Pattern p = Pattern.compile("<sip:([^@]+)@([^>]+)>");
+                    Matcher m = p.matcher(call.getNumber());
+                    if (m.find()) {
+
+                        if (historyEntries.containsKey(m.group(1) + "@" + m.group(2))) {
+                            historyEntries.get(m.group(1) + "@" + m.group(2)).addHistoryCall(call, contact);
+                        } else {
+                            HistoryEntry e = new HistoryEntry(call.getAccountID(), contact);
+                            e.addHistoryCall(call, contact);
+                            historyEntries.put(m.group(1) + "@" + m.group(2), e);
+                        }
+
+                    } else {
+                        HistoryEntry e = new HistoryEntry(call.getAccountID(), contact);
+                        e.addHistoryCall(call, contact);
+                        historyEntries.put(call.getNumber(), e);
+                    }
+
+                }
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+
+/*
+        try {
+            ArrayList<HashMap<String, String>> history = (ArrayList<HashMap<String, String>>) service.getHistory();
+
+            for (HashMap<String, String> entry : history) {
+
+                CallContact contact;
+                String contactName = entry.get(ServiceConstants.history.DISPLAY_NAME_KEY);
+                String number_called = entry.get(ServiceConstants.history.PEER_NUMBER_KEY);
+                if (contactName.isEmpty()) {
+                    contact = ContactBuilder.buildUnknownContact(number_called);
+                } else {
+                    contact = ContactBuilder.getInstance().buildSimpleContact(contactName, number_called);
+                }
+
+                if (historyEntries.containsKey(number_called)) {
+                    // It's a direct match
+                    historyEntries.get(number_called).addHistoryCall(new HistoryCall(entry), contact);
+                } else {
+                    // Maybe we can extract the extension @ account pattern
+                    Pattern p = Pattern.compile("<sip:([^@]+)@([^>]+)>");
+                    Matcher m = p.matcher(number_called);
+                    if (m.find()) {
+
+                        if (historyEntries.containsKey(m.group(1) + "@" + m.group(2))) {
+                            historyEntries.get(m.group(1) + "@" + m.group(2)).addHistoryCall(new HistoryCall(entry), contact);
+                        } else {
+                            HistoryEntry e = new HistoryEntry(entry.get(ServiceConstants.history.ACCOUNT_ID_KEY), contact);
+                            e.addHistoryCall(new HistoryCall(entry), contact);
+                            historyEntries.put(m.group(1) + "@" + m.group(2), e);
+                        }
+
+                    } else {
+
+                        HistoryEntry e = new HistoryEntry(entry.get(ServiceConstants.history.ACCOUNT_ID_KEY), contact);
+                        e.addHistoryCall(new HistoryCall(entry), contact);
+                        historyEntries.put(number_called, e);
+                    }
+
+                }
+
+            }
+
+        } catch (RemoteException e) {
+            Log.i(TAG, e.toString());
+        }*/
+        return new ArrayList<HistoryEntry>(historyEntries.values());
+    }
+
+
+}
diff --git a/ring-android/src/cx/ring/loaders/LoaderConstants.java b/ring-android/src/cx/ring/loaders/LoaderConstants.java
new file mode 100644
index 0000000..2d71312
--- /dev/null
+++ b/ring-android/src/cx/ring/loaders/LoaderConstants.java
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.loaders;
+
+public class LoaderConstants {
+    
+    public static final int CONTACT_LOADER = 0;
+    public static final int ACCOUNTS_LOADER = 1;
+    public static final int HISTORY_LOADER = 2;
+
+}
diff --git a/ring-android/src/cx/ring/model/Attractor.java b/ring-android/src/cx/ring/model/Attractor.java
new file mode 100644
index 0000000..8240c57
--- /dev/null
+++ b/ring-android/src/cx/ring/model/Attractor.java
@@ -0,0 +1,130 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@gmail.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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+public class Attractor {
+
+    public interface Callback {
+
+		/**
+		 * Called when a bubble is on the "active" zone of the attractor.
+		 * 
+		 * @param b The bubble that is on the attractor.
+		 * @return true if the bubble should be removed from the model, false otherwise.
+		 */
+		public boolean onBubbleSucked(Bubble b);
+	}
+
+    public enum Type {
+        POINT, BORDER
+    }
+
+    final Callback callback;
+    final Type type;
+
+    private final RectF bounds = new RectF();
+    private final RectF boundsScaled = new RectF();
+    final PointF pos = new PointF();
+	final float radius;
+    private final Bitmap img;
+    final String name;
+
+	public Attractor(PointF pos, float size, Callback callback, Bitmap img) {
+        this.type = Type.POINT;
+        this.callback = callback;
+        this.pos.set(pos);
+		this.radius = size/2;
+		this.img = img;
+        this.name = null;
+        setBounds();
+	}
+
+	public Attractor(PointF pos, float radius, Callback callback, Context c, int resId) {
+		this(pos, radius, callback, BitmapFactory.decodeResource(c.getResources(), resId));
+	}
+
+    public Attractor(String name, float size, Callback callback, Bitmap img) {
+        this.type = Type.POINT;
+        this.name = name;
+        this.callback = callback;
+        this.radius = size/2;
+        this.img = img;
+        setBounds();
+    }
+
+    public void setSize(float w, float h)
+    {
+        if (type != Type.BORDER)
+            return;
+        pos.set(w, h);
+        setBounds();
+    }
+
+    public void setPos(float x, float y) {
+        pos.set(x, y);
+        setBounds();
+    }
+
+    private void setBounds() {
+        bounds.set(pos.x - radius, pos.y - radius, pos.x + radius, pos.y + radius);
+    }
+
+	public RectF getBounds() {
+		return bounds;
+	}
+
+    public RectF getBounds(float scale) {
+        float r = radius * scale;
+        boundsScaled.set(pos.x - r, pos.y - r, pos.x + r, pos.y + r);
+        return boundsScaled;
+    }
+
+    public RectF getBounds(float scale, PointF start, float d) {
+        float r = radius * scale;
+        float md = 1.f - d;
+        float x = pos.x * d + start.x * md;
+        float y = pos.y * d + start.y * md;
+        boundsScaled.set(x - r, y - r, x + r, y + r);
+        return boundsScaled;
+    }
+
+	public Bitmap getBitmap() {
+		return img;
+	}
+
+}
diff --git a/ring-android/src/cx/ring/model/Bubble.java b/ring-android/src/cx/ring/model/Bubble.java
new file mode 100644
index 0000000..3cf6e8a
--- /dev/null
+++ b/ring-android/src/cx/ring/model/Bubble.java
@@ -0,0 +1,243 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.content.Context;
+import android.graphics.*;
+import android.graphics.Paint.Style;
+import cx.ring.R;
+import cx.ring.adapters.ContactPictureTask;
+
+public abstract class Bubble {
+
+    protected PointF pos = new PointF();
+    protected RectF bounds;
+    private float targetScale = 1.f;
+    protected float radius;
+    protected float scale = .1f;
+    public PointF speed = new PointF(0, 0);
+    //public PointF last_speed = new PointF();
+    public final PointF attractionPoint;
+    public Attractor attractor = null;
+
+    public boolean isUser;
+
+    private boolean grabbed = false;
+    private long lastDrag;
+
+    public boolean markedToDie = false;
+    //public long lastTime = System.nanoTime();
+
+    // A Bitmap object that is going to be passed to the BitmapShader
+    protected Bitmap externalBMP;
+    protected Bitmap savedPhoto;
+
+    protected Context mContext;
+
+    public Bubble(Context context, CallContact contact, float x, float y, float size) {
+        mContext = context;
+        pos.set(x, y);
+        radius = size / 2; // 10 is the white stroke
+        savedPhoto = getContactPhoto(context, contact, (int) size);
+        generateBitmap();
+        attractionPoint = new PointF(x, y);
+        isUser = false;
+    }
+
+    public void update(float dt) {
+        setScale(scale + (targetScale - scale) * dt * 5.f);
+    }
+
+    public void grab() {
+        grabbed = true;
+        lastDrag = System.nanoTime();
+        targetScale = .8f;
+    }
+
+    public void ungrab() {
+        grabbed = false;
+        targetScale = 1.f;
+    }
+
+    public void close() {
+        markedToDie = true;
+        targetScale = .1f;
+    }
+
+    public void drag(float x, float y) {
+        long now = System.nanoTime();
+        float dt = (float) ((now - lastDrag) / 1000000000.);
+        float dx = x - pos.x, dy = y - pos.y;
+        lastDrag = now;
+        setPos(x, y);
+        speed.x = dx / dt;
+        speed.y = dy / dt;
+    }
+
+    public void setTargetScale(float t) {
+        targetScale = t;
+    }
+
+    public boolean isGrabbed() {
+        return grabbed;
+    }
+
+    protected void generateBitmap() {
+
+        int w = savedPhoto.getWidth(), h = savedPhoto.getHeight();
+        if (w > h) {
+            w = h;
+        } else if (h > w) {
+            h = w;
+        }
+        externalBMP = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+        BitmapShader shader;
+        shader = new BitmapShader(savedPhoto, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+        Paint paint = new Paint();
+        paint.setDither(true);
+        paint.setAntiAlias(true);
+        paint.setShader(shader);
+        Canvas internalCanvas = new Canvas(externalBMP);
+        internalCanvas.drawCircle(w / 2, h / 2, w / 2, paint);
+
+        Paint mLines = new Paint();
+        mLines.setStyle(Style.STROKE);
+        mLines.setStrokeWidth(8);
+        mLines.setColor(Color.WHITE);
+
+        mLines.setDither(true);
+        mLines.setAntiAlias(true);
+        internalCanvas.drawCircle(w / 2, h / 2, w / 2 - 4, mLines);
+
+        bounds = new RectF(pos.x - getRadius(), pos.y - getRadius(), pos.x + getRadius(), pos.y + getRadius());
+    }
+
+    protected Bitmap getContactPhoto(Context context, CallContact contact, int size) {
+        if (contact.getPhoto_id() > 0) {
+            return ContactPictureTask.loadContactPhoto(context.getContentResolver(), contact.getId());
+        } else {
+            return ContactPictureTask.decodeSampledBitmapFromResource(context.getResources(), R.drawable.ic_contact_picture, size, size);
+        }
+    }
+
+    public Bitmap getBitmap() {
+        return externalBMP;
+    }
+
+    public RectF getBounds() {
+        return bounds;
+    }
+
+    public void set(float x, float y, float s) {
+        scale = s;
+        pos.x = x;
+        pos.y = y;
+        bounds.set(pos.x - getRadius(), pos.y - getRadius(), pos.x + getRadius(), pos.y + getRadius());
+    }
+
+    public float getPosX() {
+        return pos.x;
+    }
+
+    public float getPosY() {
+        return pos.y;
+    }
+
+    public void setPos(float x, float y) {
+        set(x, y, scale);
+    }
+
+    public PointF getPos() {
+        return pos;
+    }
+
+    public float getScale() {
+        return scale;
+    }
+
+    public void setScale(float s) {
+        set(pos.x, pos.y, s);
+    }
+
+    public int getRadius() {
+        return (int) (radius * scale);
+    }
+
+    /**
+     * Point intersection test.
+     */
+    boolean intersects(float x, float y) {
+        float dx = x - pos.x;
+        float dy = y - pos.y;
+
+        return dx * dx + dy * dy < getRadius() * getRadius();
+    }
+
+    /**
+     * Other circle intersection test.
+     */
+    boolean intersects(float x, float y, float radius) {
+        float dx = x - pos.x, dy = y - pos.y;
+        float tot_radius = getRadius() + radius;
+        return dx * dx + dy * dy < tot_radius * tot_radius;
+    }
+
+    public boolean isOnBorder(float w, float h) {
+        return (bounds.left < 0 || bounds.right > w || bounds.top < 0 || bounds.bottom > h);
+    }
+
+    /**
+     * Always return the normal radius of the bubble
+     * 
+     * @return
+     */
+    public float getRetractedRadius() {
+        return radius;
+    }
+
+    public abstract boolean getHoldStatus();
+
+    public abstract boolean getRecordStatus();
+
+    public abstract String getName();
+
+    public abstract boolean callIDEquals(String call);
+    
+    public abstract String getCallID();
+
+    public boolean isConference() {
+        return false;
+    }
+
+
+}
diff --git a/ring-android/src/cx/ring/model/BubbleContact.java b/ring-android/src/cx/ring/model/BubbleContact.java
new file mode 100644
index 0000000..e21f98a
--- /dev/null
+++ b/ring-android/src/cx/ring/model/BubbleContact.java
@@ -0,0 +1,78 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.content.Context;
+
+public class BubbleContact extends Bubble {
+
+    public SipCall associated_call;
+
+    public BubbleContact(Context context, SipCall call, float x, float y, float size) {
+        super(context, call.getmContact(), x, y, size);
+        associated_call = call;
+    }
+
+    @Override
+    public boolean getHoldStatus() {
+        return associated_call.isOnHold();
+    }
+
+    @Override
+    public boolean getRecordStatus() {
+        return associated_call.isRecording();
+    }
+
+    public SipCall getCall() {
+        return associated_call;
+    }
+
+    public void setCall(SipCall call) {
+        associated_call = call;
+    }
+
+    @Override
+    public String getName() {
+        return associated_call.getmContact().getmDisplayName();
+    }
+
+    @Override
+    public boolean callIDEquals(String call) {
+        return associated_call.getCallId().contentEquals(call);
+    }
+
+    @Override
+    public String getCallID() {
+        return associated_call.getCallId();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/BubbleModel.java b/ring-android/src/cx/ring/model/BubbleModel.java
new file mode 100644
index 0000000..221ff2e
--- /dev/null
+++ b/ring-android/src/cx/ring/model/BubbleModel.java
@@ -0,0 +1,432 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.graphics.Bitmap;
+import android.graphics.PointF;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BubbleModel {
+    private static final String TAG = BubbleModel.class.getSimpleName();
+
+    public enum State {
+        None, Incoming, Outgoing, Incall
+    }
+
+    public interface ModelCallback {
+        public void bubbleGrabbed(Bubble b);
+
+        /**
+         * A bubble is put beyond view borders.
+         * @param b The bubble
+         * @return true if the bubble should be ejected, false if it should bounce.
+         */
+        public boolean bubbleEjected(Bubble b);
+    }
+
+    public interface ActionGroupCallback {
+
+        /**
+         * Called when a bubble is on the "active" zone of the attractor.
+         *
+         * @param b The bubble that is on the attractor.
+         * @return true if the bubble should be removed from the model, false otherwise.
+         */
+        public boolean onBubbleAction(Bubble b, int action);
+    }
+
+    static public class ActionGroup {
+        public boolean enabled = true;
+        private final ArrayList<Attractor> buttons = new ArrayList<Attractor>();
+        private final ActionGroupCallback callback;
+        final private float margin;
+        public Bubble bubble = null;
+        public long viewStart = 0;
+        private final float appearTime;
+        private final float disappearTime;
+
+        public ActionGroup(ActionGroupCallback cb, float btn_margin, float appear_time, float disapear_time) {
+            this.callback = cb;
+            this.margin = btn_margin;
+            appearTime = appear_time;
+            disappearTime = disapear_time;
+        }
+
+        public ArrayList<Attractor> getActions() {
+            return buttons;
+        }
+
+        public void addAction(final int id, Bitmap btn, String name, float size) {
+            final Attractor a = new Attractor(name, size, new Attractor.Callback() {
+                @Override
+                public boolean onBubbleSucked(Bubble b) {
+                    if (!enabled) return false;
+                    return callback.onBubbleAction(b, id);
+                }
+            }, btn);
+            buttons.add(a);
+        }
+
+        public void show(Bubble b) {
+            this.bubble = b;
+            long now = System.nanoTime();
+            double dt = (now - viewStart) / 1000000000.;
+            double r = 1. - Math.min(dt / disappearTime, 1.);
+            this.enabled = true;
+            this.viewStart = now - (long)(r * appearTime * 1000000000.);
+        }
+
+        public void hide() {
+            long now = System.nanoTime();
+            double dt = (now - viewStart) / 1000000000.;
+            double r = 1. - Math.min(dt / appearTime, 1.);
+            this.enabled = false;
+            viewStart = now - (long)(r * disappearTime * 1000000000.);
+        }
+
+        public float getVisibility(long now) {
+            double dt = (now - viewStart) / 1000000000.;
+            if (enabled)
+                return (float) Math.min(dt / appearTime, 1.);
+            else
+                return 1.f - (float) Math.min(dt / disappearTime, 1.);
+        }
+
+        public void order(int w, int h) {
+            int n = buttons.size();
+            if (n == 0) return;
+            //float y = h - 3 * buttons.get(0).radius;
+            float y = bubble.getPosY() - margin - bubble.radius;
+            final float WIDTH = 2 * buttons.get(0).radius;
+            float totw = n * WIDTH + (n-1) * margin;
+            float xs = (w - totw) / 2 + buttons.get(0).radius;
+            float xstep = WIDTH+margin;
+            for (int i=0; i<n; i++) {
+                buttons.get(i).setPos(xs + i*xstep, y);
+            }
+        }
+    }
+
+    private final ModelCallback callback;
+
+    private long lastUpdate = 0;
+    private int width, height;
+    private final ArrayList<Bubble> bubbles = new ArrayList<Bubble>();
+    private final ArrayList<Attractor> attractors = new ArrayList<Attractor>();
+    private ActionGroup actions = null;
+
+    private static final double BUBBLE_RETURN_TIME_HALF_LIFE = .3;
+    private static final double BUBBLE_RETURN_TIME_LAMBDA = Math.log(2) / BUBBLE_RETURN_TIME_HALF_LIFE;
+
+    private static final double FRICTION_VISCOUS = Math.log(2) / .2f; // Viscous friction factor
+
+    private static final float BUBBLE_MAX_SPEED = 2500.f; // px.s⁻¹ : Max target speed in px/sec
+    private static final float ATTRACTOR_SMOOTH_DIST = 50.f; // px : Size of the "gravity hole" around the attractor
+    private static final float ATTRACTOR_STALL_DIST = 15.f; // px : Size of the "gravity hole" flat bottom
+    private static final float ATTRACTOR_DIST_SUCK = 20.f; // px
+
+    private static final float BORDER_REPULSION = 60000; // px.s⁻²
+
+    private final float border_repulsion;
+    private final float bubble_max_speed;
+    private final float attractor_smooth_dist;
+    private final float attractor_stall_dist;
+    private final float attractor_dist_suck;
+
+    private final float density;
+
+    private float circle_radius;
+    private final PointF circle_center = new PointF();
+
+    public State curState = State.None;
+
+    public BubbleModel(float screen_density, ModelCallback cb) {
+        Log.d(TAG, "Creating BubbleModel");
+        callback = cb;
+        this.density = screen_density;
+        attractor_dist_suck = ATTRACTOR_DIST_SUCK * density;
+        bubble_max_speed = BUBBLE_MAX_SPEED * density;
+        attractor_smooth_dist = ATTRACTOR_SMOOTH_DIST * density;
+        attractor_stall_dist = ATTRACTOR_STALL_DIST * density;
+        border_repulsion = BORDER_REPULSION * density;
+    }
+
+    public void setSize(int w, int h, float bubble_sz)
+    {
+        width = w;
+        height = h;
+        for (Attractor a : attractors) {
+            a.setSize(w, h);
+        }
+        if (actions != null) {
+            actions.order(width, height);
+        }
+        circle_radius = Math.min(width, height) / 2 - bubble_sz;
+        circle_center.set(width/2, height/2);
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public float getCircleSize() {
+        return circle_radius;
+    }
+
+    public PointF getCircleCenter() {
+        return circle_center;
+    }
+
+    public void addBubble(Bubble b) {
+        bubbles.add(b);
+    }
+
+    public List<Bubble> getBubbles() {
+        return bubbles;
+    }
+
+    public Bubble getBubble(String call) {
+        for (Bubble b : bubbles) {
+            if (!b.isUser && b.callIDEquals(call))
+                return b;
+        }
+        return null;
+    }
+
+    public void removeBubble(SipCall sipCall) {
+        bubbles.remove(getBubble(sipCall.getCallId()));
+
+    }
+
+    public void addAttractor(Attractor a) {
+        attractors.add(a);
+    }
+
+    public List<Attractor> getAttractors() {
+        return attractors;
+    }
+
+    public void clearAttractors() {
+        attractors.clear();
+    }
+
+    public void setActions(Bubble b, ActionGroup actions) {
+        actions.show(b);
+        /*actions.enabled = true;
+        actions.viewStart = 0;*/
+        actions.order(width, height);
+        this.actions = actions;
+    }
+
+    public ActionGroup getActions() {
+        return actions;
+    }
+
+    public void clearActions() {
+        this.actions = null;
+    }
+
+    public Bubble getUser() {
+        for (Bubble b : bubbles) {
+            if (b.isUser)
+                return b;
+        }
+        return null;
+    }
+
+    public void clear() {
+        clearAttractors();
+        bubbles.clear();
+    }
+
+    public void grabBubble(Bubble b) {
+        b.grab();
+        callback.bubbleGrabbed(b);
+    }
+
+    public void ungrabBubble(Bubble b) {
+        b.ungrab();
+    }
+
+    public void ejectBubble(Bubble b) {
+        boolean eject = callback.bubbleEjected(b);
+        if (eject) {
+            b.close();
+        }
+    }
+
+    public void update() {
+        /* INFO: if you get a NullPointer or OutOfBounds exception here,
+         * you may have some wrong/missing locks. */
+
+        long now = System.nanoTime();
+
+        // Do nothing if lastUpdate is in the future.
+        if (lastUpdate > now)
+            return;
+
+        double ddt = Math.min((now - lastUpdate) / 1000000000.0, .2);
+        lastUpdate = now;
+
+        float dt = (float) ddt;
+
+        //int attr_n = attractors.size();
+        boolean actionAttr = false;
+
+        // Iterators should not be used in frequently called methods
+        // to avoid garbage collection glitches caused by iterator objects.
+        for (int i = 0, n = bubbles.size(); i < n; i++) {
+            Bubble b = bubbles.get(i);
+
+            if (b.markedToDie) {
+                b.update(dt);
+                continue;
+            }
+
+            float bx = b.getPosX(), by = b.getPosY();
+
+            Attractor attractor = null;
+            PointF attractor_pos = b.attractionPoint;
+            float attractor_dist = (attractor_pos.x - bx) * (attractor_pos.x - bx) + (attractor_pos.y - by) * (attractor_pos.y - by);
+
+            boolean actionGrp = actions != null && actions.enabled && actions.bubble == b;
+            final List<Attractor> attr = (actionGrp) ? actions.getActions() : attractors;
+
+            for (Attractor t : attr) {
+                float dx = t.pos.x - bx, dy = t.pos.y - by;
+                float adist = dx * dx + dy * dy;
+                if (adist < attractor_dist) {
+                    attractor = t;
+                    attractor_pos = t.pos;
+                    attractor_dist = adist;
+                }
+            }
+
+            b.attractor = attractor;
+
+            if (!b.isGrabbed()) {
+                if (actionGrp) {
+                    for (Attractor anAttr : attr) {
+                        if (anAttr == attractor) {
+                            actionAttr = true;
+                            break;
+                        }
+                    }
+                }
+
+                // float friction_coef = 1.f-FRICTION_VISCOUS*dt;
+                double friction_coef = 1 + Math.expm1(-FRICTION_VISCOUS * ddt);
+                b.speed.x *= friction_coef;
+                b.speed.y *= friction_coef;
+
+                float target_speed;
+                float tdx = attractor_pos.x - bx, tdy = attractor_pos.y - by;
+                float dist = Math.max(1.f, (float) Math.sqrt(tdx * tdx + tdy * tdy));
+                if (dist > attractor_smooth_dist)
+                    target_speed = bubble_max_speed;
+                else if (dist < attractor_stall_dist)
+                    target_speed = 0;
+                else {
+                    float a = (dist - attractor_stall_dist) / (attractor_smooth_dist - attractor_stall_dist);
+                    target_speed = bubble_max_speed * a;
+                }
+                if (attractor != null) {
+                    if (dist > attractor_smooth_dist)
+                        b.setTargetScale(1.f);
+                    else if (dist < attractor_stall_dist)
+                        b.setTargetScale(2f);
+                    else {
+                        float a = (dist - attractor_stall_dist) / (attractor_smooth_dist - attractor_stall_dist);
+                        b.setTargetScale(a * .8f + .2f);
+                    }
+                }
+
+                // border repulsion
+
+                if (bx < 0 && b.speed.x < 0) {
+                    b.speed.x += dt * border_repulsion;
+                } else if (bx > width && b.speed.x > 0) {
+                    b.speed.x -= dt * border_repulsion;
+                }
+                if (by < 0 && b.speed.y < 0) {
+                    b.speed.y += dt * border_repulsion;
+                } else if (by > height && b.speed.y > 0) {
+                    b.speed.y -= dt * border_repulsion;
+                }
+
+
+                b.speed.x += dt * target_speed * tdx / dist;
+                b.speed.y += dt * target_speed * tdy / dist;
+
+                double edt = -Math.expm1(-BUBBLE_RETURN_TIME_LAMBDA * ddt);
+                double dx = (attractor_pos.x - bx) * edt + Math.min(bubble_max_speed, b.speed.x) * dt;
+                double dy = (attractor_pos.y - by) * edt + Math.min(bubble_max_speed, b.speed.y) * dt;
+                // Log.w(TAG, "update dx="+dt+" dy="+dy);
+                b.setPos((float) (bx + dx), (float) (by + dy));
+
+                /*if (b.isOnBorder(width, height)) {
+                    ejectBubble(b);
+                }*/
+
+                if (attractor != null && attractor_dist < attractor_dist_suck * attractor_dist_suck) {
+                    boolean removeBubble = attractor.callback.onBubbleSucked(b);
+                    if (removeBubble) {
+                        bubbles.remove(b);
+                        n--;
+                    } else {
+                        b.setTargetScale(1.f);
+                    }
+
+                    if (actionGrp) {
+                        actions.hide();
+                    }
+                }
+            } else {
+                actionAttr = true;
+            }
+
+            b.update(dt);
+        }
+
+        if (actions != null && actions.enabled && !actionAttr) {
+            actions.hide();
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/BubbleUser.java b/ring-android/src/cx/ring/model/BubbleUser.java
new file mode 100644
index 0000000..d79e5c2
--- /dev/null
+++ b/ring-android/src/cx/ring/model/BubbleUser.java
@@ -0,0 +1,88 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.content.Context;
+import cx.ring.R;
+
+public class BubbleUser extends Bubble {
+
+    public Conference associated_call;
+
+    public BubbleUser(Context context, CallContact m, Conference conf, float x, float y, float size) {
+        super(context, m, x, y, size);
+        isUser = true;
+        associated_call = conf;
+    }
+
+    @Override
+    public boolean getHoldStatus() {
+        return associated_call.isOnHold();
+    }
+
+    @Override
+    public boolean getRecordStatus() {
+        return associated_call.isRecording();
+    }
+
+    public Conference getConference() {
+        return associated_call;
+    }
+
+    public void setConference(Conference c) {
+        associated_call = c;
+    }
+
+    @Override
+    public String getName() {
+        return mContext.getResources().getString(R.string.me);
+    }
+
+    @Override
+    public boolean callIDEquals(String call) {
+        return associated_call.getId().contentEquals(call);
+    }
+
+    @Override
+    public String getCallID() {
+        if (associated_call.hasMultipleParticipants())
+            return associated_call.getId();
+        else
+            return associated_call.getParticipants().get(0).getCallId();
+    }
+
+    @Override
+    public boolean isConference() {
+        return associated_call.hasMultipleParticipants();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/BubblesView.java b/ring-android/src/cx/ring/model/BubblesView.java
new file mode 100644
index 0000000..8b465dd
--- /dev/null
+++ b/ring-android/src/cx/ring/model/BubblesView.java
@@ -0,0 +1,426 @@
+/*

+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.

+ *

+ *  Author: Alexandre Lision <alexandre.lision@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.

+ *

+ *  Additional permission under GNU GPL version 3 section 7:

+ *

+ *  If you modify this program, or any covered work, by linking or

+ *  combining it with the OpenSSL project's OpenSSL library (or a

+ *  modified version of that library), containing parts covered by the

+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.

+ *  grants you additional permission to convey the resulting work.

+ *  Corresponding Source for a non-source form of such a combination

+ *  shall include the source code for the parts of OpenSSL used as well

+ *  as that of the covered work.

+ */

+

+package cx.ring.model;

+

+import android.content.Context;

+import android.content.res.Resources;

+import android.graphics.*;

+import android.graphics.Paint.Align;

+import android.opengl.GLSurfaceView;

+import android.util.AttributeSet;

+import android.util.FloatMath;

+import android.util.Log;

+import android.view.GestureDetector;

+import android.view.GestureDetector.OnGestureListener;

+import android.view.MotionEvent;

+import android.view.SurfaceHolder;

+import android.view.View;

+import android.view.View.OnTouchListener;

+import android.view.animation.DecelerateInterpolator;

+import android.view.animation.Interpolator;

+import android.view.animation.OvershootInterpolator;

+import cx.ring.R;

+

+import java.util.List;

+

+public class BubblesView extends GLSurfaceView implements SurfaceHolder.Callback, OnTouchListener {

+    private static final String TAG = BubblesView.class.getSimpleName();

+

+    private BubblesThread thread = null;

+    private BubbleModel model;

+

+    private Paint black_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);

+    private Paint white_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);

+    private Paint canvas_paint = new Paint();

+    private Paint circle_paint = new Paint(Paint.ANTI_ALIAS_FLAG);

+    private Paint action_paint = new Paint();

+

+    static private final Interpolator interpolator = new OvershootInterpolator(2.f);

+    static private final Interpolator interpolator_dec = new DecelerateInterpolator();

+

+    private final Bitmap ic_bg;

+    private final Bitmap ic_bg_sel;

+

+    private GestureDetector gDetector;

+

+    //private float density;

+    private float textDensity;

+    private float bubbleActionTextDistMin;

+    private float bubbleActionTextDistMax;

+

+    private boolean dragging_bubble = false;

+

+    public BubblesView(Context context, AttributeSet attrs) {

+        super(context, attrs);

+

+        final Resources r = getResources();

+        //density = r.getDisplayMetrics().density;

+        textDensity = r.getDisplayMetrics().scaledDensity;

+        bubbleActionTextDistMin = r.getDimension(R.dimen.bubble_action_textdistmin);

+        bubbleActionTextDistMax = r.getDimension(R.dimen.bubble_action_textdistmax);

+

+        ic_bg = BitmapFactory.decodeResource(r, R.drawable.ic_bg);

+        ic_bg_sel = BitmapFactory.decodeResource(r, R.drawable.ic_bg_sel);

+

+        if (isInEditMode()) return;

+

+        SurfaceHolder holder = getHolder();

+        holder.addCallback(this);

+

+        this.setZOrderOnTop(true); // necessary

+        holder.setFormat(PixelFormat.TRANSLUCENT);

+        // create thread only; it's started in surfaceCreated()

+        createThread();

+

+        setOnTouchListener(this);

+        setFocusable(true);

+

+        black_name_paint.setTextSize(18 * textDensity);

+        black_name_paint.setColor(0xFF303030);

+        black_name_paint.setTextAlign(Align.CENTER);

+

+        white_name_paint.setTextSize(18 * textDensity);

+        white_name_paint.setColor(0xFFEEEEEE);

+        white_name_paint.setTextAlign(Align.CENTER);

+

+        circle_paint.setStyle(Paint.Style.STROKE);

+        circle_paint.setColor(r.getColor(R.color.darker_gray));

+        circle_paint.setXfermode(null);

+

+        gDetector = new GestureDetector(getContext(), new BubbleGestureListener());

+        gDetector.setIsLongpressEnabled(false);

+    }

+

+    private void createThread() {

+        if (thread != null)

+            return;

+        thread = new BubblesThread(getHolder(), getContext());

+        if (model != null)

+            thread.setModel(model);

+    }

+

+    public void setModel(BubbleModel model) {

+        this.model = model;

+        thread.setModel(model);

+    }

+

+    /*

+     * @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { thread.pause(); } }

+     */

+

+    @Override

+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

+        Log.w(TAG, "surfaceChanged " + width + "-" + height);

+        /*if (height < model.getHeight()) // probably showing the keyboard, don't move!

+            return;

+

+        thread.setSurfaceSize(width, height);*/

+    }

+

+    /*

+     * Callback invoked when the Surface has been created and is ready to be used.

+     */

+    @Override

+    public void surfaceCreated(SurfaceHolder holder) {

+        // start the thread here so that we don't busy-wait in run()

+        // waiting for the surface to be created

+        createThread();

+

+        Log.w(TAG, "surfaceCreated");

+        thread.setRunning(true);

+        thread.start();

+    }

+

+    /*

+     * Callback invoked when the Surface has been destroyed and must no longer be touched. WARNING: after this method returns, the Surface/Canvas must

+     * never be touched again!

+     */

+    @Override

+    public void surfaceDestroyed(SurfaceHolder holder) {

+        // we have to tell thread to shut down & wait for it to finish, or else

+        // it might touch the Surface after we return and explode

+        Log.w(TAG, "surfaceDestroyed");

+        boolean retry = true;

+        thread.setRunning(false);

+        thread.setPaused(false);

+        while (retry) {

+            try {

+                Log.w(TAG, "joining...");

+                thread.join();

+                retry = false;

+            } catch (InterruptedException ignored) {

+            }

+        }

+        Log.w(TAG, "done");

+        thread = null;

+    }

+

+    public boolean isDraggingBubble() {

+        return dragging_bubble;

+    }

+

+    class BubblesThread extends Thread {

+        private boolean running = false;

+        public boolean suspendFlag = false;

+        private SurfaceHolder surfaceHolder;

+

+        BubbleModel model = null;

+

+        public BubblesThread(SurfaceHolder holder, Context context) {

+            surfaceHolder = holder;

+        }

+

+        public void setModel(BubbleModel model) {

+            this.model = model;

+        }

+

+        @Override

+        public void run() {

+            while (running) {

+                Canvas c = null;

+                try {

+

+                    if (suspendFlag) {

+                        synchronized (this) {

+                            while (suspendFlag) {

+                                try {

+                                    wait();

+                                } catch (InterruptedException e) {

+                                    // TODO Auto-generated catch block

+                                    e.printStackTrace();

+                                }

+                            }

+                        }

+                    } else {

+                        c = surfaceHolder.lockCanvas(null);

+

+                        // for the case the surface is destroyed while already in the loop

+                        if (c == null || model == null)

+                            continue;

+

+                        synchronized (model) {

+                            model.update();

+                        }

+                        synchronized (surfaceHolder) {

+                            // Log.w(TAG, "Thread doDraw");

+                            synchronized (model) {

+                                doDraw(c);

+                            }

+                        }

+                    }

+

+                } finally {

+                    if (c != null)

+                        surfaceHolder.unlockCanvasAndPost(c);

+                }

+            }

+        }

+

+        public void setPaused(boolean wantToPause) {

+            synchronized (this) {

+                suspendFlag = wantToPause;

+                notify();

+            }

+        }

+

+        public void setRunning(boolean b) {

+            running = b;

+        }

+

+        /**

+         * got multiple IndexOutOfBoundsException, when switching calls. //FIXME

+         *

+         * @param canvas

+         */

+        private void doDraw(Canvas canvas) {

+            List<Bubble> bubbles = model.getBubbles();

+            List<Attractor> attractors = model.getAttractors();

+            BubbleModel.ActionGroup actions = model.getActions();

+

+            long now = System.nanoTime();

+

+            canvas_paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

+            canvas.drawPaint(canvas_paint);

+            canvas_paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));

+

+            if (model.curState == BubbleModel.State.Incall || model.curState == BubbleModel.State.Outgoing) {

+                PointF center = model.getCircleCenter();

+                canvas.drawCircle(center.x, center.y, model.getCircleSize(), circle_paint);

+            }

+

+            for (Attractor a : attractors) {

+                canvas.drawBitmap(a.getBitmap(), null, a.getBounds(), null);

+            }

+

+            Bubble drawLater = (actions == null) ? null : actions.bubble;

+            for (Bubble b : bubbles) {

+                if (b == drawLater) continue;

+                canvas.drawBitmap(b.getBitmap(), null, b.getBounds(), null);

+                canvas.drawText(b.getName(), b.getPosX(), b.getPosY() - b.getRetractedRadius() * 1.2f, getNamePaint(b));

+            }

+

+            if (actions != null) {

+                float t = actions.getVisibility(now);

+                if (!actions.enabled && t == .0f) {

+                    model.clearActions();

+                }

+                float showed = interpolator.getInterpolation(t);

+                float dark = interpolator_dec.getInterpolation(t);

+                float dist_range = bubbleActionTextDistMax - bubbleActionTextDistMin;

+                action_paint.setAlpha((int) (255 * t));

+

+                List<Attractor> acts = actions.getActions();

+                Bubble b = actions.bubble;

+

+                canvas.drawARGB((int)(dark*128), 0, 0, 0);

+

+                white_name_paint.setTextSize(18 * textDensity);

+                boolean suck_bubble = false;

+                for (Attractor a : acts) {

+                    if (b.attractor == a) {

+                        canvas.drawBitmap(ic_bg_sel, null, a.getBounds(showed * 2.f, b.getPos(), showed), action_paint);

+                        suck_bubble = true;

+                    } else

+                        canvas.drawBitmap(ic_bg, null, a.getBounds(showed * 2.f, b.getPos(), showed), action_paint);

+                    canvas.drawBitmap(a.getBitmap(), null, a.getBounds(showed, b.getPos(), showed), null);

+                    float dist_raw = FloatMath.sqrt((b.pos.x - a.pos.x) * (b.pos.x - a.pos.x) + (b.pos.y - a.pos.y) * (b.pos.y - a.pos.y));

+                    float dist_min = a.radius + b.radius + bubbleActionTextDistMin;

+                    float dist = Math.max(0, dist_raw - dist_min);

+                    if (actions.enabled && dist < dist_range) {

+                        white_name_paint.setAlpha(255 - (int) (255 * dist / dist_range));

+                        canvas.drawText(a.name, a.getBounds().centerX(), a.getBounds().centerY() - a.radius * 2.2f, white_name_paint);

+                    }

+                }

+                white_name_paint.setAlpha(255);

+

+                canvas.drawBitmap(drawLater.getBitmap(), null, drawLater.getBounds(), (!actions.enabled && suck_bubble)? action_paint : null);

+            }

+        }

+    }

+

+    private Paint getNamePaint(Bubble b) {

+        black_name_paint.setTextSize(18/* * b.targetScale */ * textDensity);

+        return black_name_paint;

+    }

+

+    @Override

+    public boolean onTouch(View v, MotionEvent event) {

+        // Log.w(TAG, "onTouch " + event.getAction());

+

+        int action = event.getActionMasked();

+

+        if (gDetector.onTouchEvent(event))

+            return true;

+

+        if (action == MotionEvent.ACTION_UP) {

+            if (thread.suspendFlag) {

+                Log.i(TAG, "Relaunch drawing thread");

+                thread.setPaused(false);

+            }

+            List<Bubble> bubbles = model.getBubbles();

+            for (Bubble b : bubbles) {

+                if (b.isGrabbed()) {

+                    model.ungrabBubble(b);

+                }

+            }

+            dragging_bubble = false;

+        } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) {

+            Log.i(TAG, "Not dragging thread should be stopped");

+            thread.setPaused(true);

+        }

+        return true;

+    }

+

+    public void restartDrawing() {

+        if (thread != null && thread.suspendFlag) {

+            Log.i(TAG, "Relaunch drawing thread");

+            thread.setPaused(false);

+        }

+    }

+

+    public void stopThread() {

+        if (thread != null && thread.suspendFlag) {

+            Log.i(TAG, "Stop drawing thread");

+            thread.setPaused(true);

+        }

+    }

+

+    class BubbleGestureListener implements OnGestureListener {

+        @Override

+        public boolean onDown(MotionEvent event) {

+            synchronized (model) {

+                List<Bubble> bubbles = model.getBubbles();

+                for (Bubble b : bubbles) {

+                    if (b.intersects(event.getX(), event.getY())) {

+                        model.grabBubble(b);

+                        b.setPos(event.getX(), event.getY());

+                        dragging_bubble = true;

+                    }

+                }

+            }

+            return true;

+        }

+

+        @Override

+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

+            return false;

+        }

+

+        @Override

+        public void onLongPress(MotionEvent e) {

+        }

+

+        @Override

+        public boolean onScroll(MotionEvent e1, MotionEvent event, float distanceX, float distanceY) {

+            synchronized (model) {

+                List<Bubble> bubbles = model.getBubbles();

+                for (Bubble b : bubbles) {

+                    if (b.isGrabbed()) {

+                        b.drag(event.getX(), event.getY());

+                        return true;

+                    }

+                }

+            }

+            return false;

+        }

+

+        @Override

+        public void onShowPress(MotionEvent e) {

+        }

+

+        @Override

+        public boolean onSingleTapUp(MotionEvent e) {

+            return false;

+        }

+    }

+}

diff --git a/ring-android/src/cx/ring/model/CallContact.java b/ring-android/src/cx/ring/model/CallContact.java
new file mode 100644
index 0000000..c067a75
--- /dev/null
+++ b/ring-android/src/cx/ring/model/CallContact.java
@@ -0,0 +1,328 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.model;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Profile;
+
+public class CallContact implements Parcelable {
+
+    public static int DEFAULT_ID = 0;
+
+    private long id;
+    private String mDisplayName;
+    private long photo_id;
+    private ArrayList<Phone> phones, sip_phones;
+    private String mEmail;
+    private boolean isUser;
+    private WeakReference<Bitmap> contact_photo = new WeakReference<Bitmap>(null);
+
+    private CallContact(long cID, String displayName, long photoID, ArrayList<Phone> p, ArrayList<Phone> sip, String mail, boolean user) {
+        id = cID;
+        mDisplayName = displayName;
+        phones = p;
+        sip_phones = sip;
+        mEmail = mail;
+        photo_id = photoID;
+        isUser = user;
+    }
+
+    public CallContact(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public String getmDisplayName() {
+        return mDisplayName;
+    }
+
+    public long getPhoto_id() {
+        return photo_id;
+    }
+
+    public void setPhoto_id(long photo_id) {
+        this.photo_id = photo_id;
+    }
+
+    public ArrayList<Phone> getPhones() {
+        return phones;
+    }
+
+    public void setPhones(ArrayList<Phone> phones) {
+        this.phones = phones;
+    }
+
+    public ArrayList<Phone> getSip_phones() {
+        return sip_phones;
+    }
+
+    public void setSip_phones(ArrayList<Phone> sip_phones) {
+        this.sip_phones = sip_phones;
+    }
+
+    public Phone getSipPhone() {
+        if (sip_phones.size() > 0) {
+            return sip_phones.get(0);
+        }
+        if (phones.size() > 0) {
+            return phones.get(0);
+        }
+        return null;
+    }
+
+    public String getmEmail() {
+        return mEmail;
+    }
+
+    public void setmEmail(String mEmail) {
+        this.mEmail = mEmail;
+    }
+
+    @Override
+    public String toString() {
+        return mDisplayName;
+    }
+
+    public static class ContactBuilder {
+
+        long contactID;
+        String contactName;
+        long contactPhoto;
+        ArrayList<Phone> phones;
+        ArrayList<Phone> sip;
+        String contactMail;
+
+        public ContactBuilder startNewContact(long id, String displayName, long photo_id) {
+            contactID = id;
+
+            contactName = displayName;
+            contactPhoto = photo_id;
+            phones = new ArrayList<Phone>();
+            sip = new ArrayList<Phone>();
+            return this;
+        }
+
+        public ContactBuilder addPhoneNumber(String num, int type) {
+            phones.add(new Phone(num, type));
+            return this;
+        }
+
+        public ContactBuilder addSipNumber(String num, int type) {
+            sip.add(new Phone(num, type));
+            return this;
+        }
+
+        public CallContact build() {
+            return new CallContact(contactID, contactName, contactPhoto, phones, sip, contactMail, false);
+        }
+
+        public static ContactBuilder getInstance() {
+            return new ContactBuilder();
+        }
+
+        public static CallContact buildUnknownContact(String to) {
+            ArrayList<Phone> phones = new ArrayList<Phone>();
+            phones.add(new Phone(to, 0));
+
+            return new CallContact(-1, to, 0, phones, new ArrayList<CallContact.Phone>(), "", false);
+        }
+
+        public static CallContact buildUserContact(ContentResolver cr) {
+            String[] mProjection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.PHOTO_ID };
+            Cursor mProfileCursor = cr.query(Profile.CONTENT_URI, mProjection, null, null, null);
+            CallContact result;
+            if (mProfileCursor.getCount() > 0) {
+                mProfileCursor.moveToFirst();
+                String displayName = mProfileCursor.getString(mProfileCursor.getColumnIndex(Profile.DISPLAY_NAME_PRIMARY));
+
+                result = new CallContact(mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile._ID)), displayName,
+                        mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile.PHOTO_ID)), new ArrayList<Phone>(),
+                        new ArrayList<CallContact.Phone>(), "", true);
+            } else {
+                result = new CallContact(-1, "Me", 0, new ArrayList<Phone>(), new ArrayList<CallContact.Phone>(), "", true);
+            }
+            mProfileCursor.close();
+            return result;
+        }
+
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(id);
+        dest.writeString(mDisplayName);
+        dest.writeLong(photo_id);
+        dest.writeTypedList(phones);
+
+        dest.writeTypedList(sip_phones);
+
+        dest.writeString(mEmail);
+        dest.writeByte((byte) (isUser ? 1 : 0));
+
+    }
+
+    private void readFromParcel(Parcel in) {
+
+        id = in.readLong();
+        mDisplayName = in.readString();
+        photo_id = in.readLong();
+        phones = new ArrayList<CallContact.Phone>();
+        sip_phones = new ArrayList<CallContact.Phone>();
+        in.readTypedList(phones, Phone.CREATOR);
+        in.readTypedList(sip_phones, Phone.CREATOR);
+        mEmail = in.readString();
+        isUser = in.readByte() == 1;
+    }
+
+    public static final Parcelable.Creator<CallContact> CREATOR = new Parcelable.Creator<CallContact>() {
+        @Override
+        public CallContact createFromParcel(Parcel in) {
+            return new CallContact(in);
+        }
+
+        @Override
+        public CallContact[] newArray(int size) {
+            return new CallContact[size];
+        }
+    };
+
+    public static class Phone implements Parcelable {
+
+        int type;
+        String number;
+
+        public Phone(String num, int ty) {
+            type = ty;
+            number = num;
+        }
+
+        public Phone(Parcel in) {
+            readFromParcel(in);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int arg1) {
+            dest.writeInt(type);
+            dest.writeString(number);
+        }
+
+        private void readFromParcel(Parcel in) {
+            type = in.readInt();
+            number = in.readString();
+        }
+
+        public static final Parcelable.Creator<Phone> CREATOR = new Parcelable.Creator<Phone>() {
+            @Override
+            public Phone createFromParcel(Parcel in) {
+                return new Phone(in);
+            }
+
+            @Override
+            public Phone[] newArray(int size) {
+                return new Phone[size];
+            }
+        };
+
+        public int getType() {
+            return type;
+        }
+
+        public void setType(int type) {
+            this.type = type;
+        }
+
+        public String getNumber() {
+            return number;
+        }
+
+        public void setNumber(String number) {
+            this.number = number;
+        }
+
+    }
+
+    public void addPhoneNumber(String tel, int type) {
+        phones.add(new Phone(tel, type));
+
+    }
+
+    public void addSipNumber(String tel, int type) {
+        sip_phones.add(new Phone(tel, type));
+
+    }
+
+    public boolean isUser() {
+        return isUser;
+    }
+
+    public boolean hasPhoto() {
+        if (contact_photo.get() != null)
+            return true;
+        return false;
+    }
+
+    public Bitmap getPhoto() {
+        return contact_photo.get();
+    }
+
+    public void setPhoto(Bitmap externalBMP) {
+        contact_photo = new WeakReference<Bitmap>(externalBMP);
+    }
+
+    /**
+     * A contact is Unknown when his name == his phone number
+     * @return true when Name == Number
+     */
+    public boolean isUnknown() {
+       return mDisplayName.contentEquals(phones.get(0).getNumber());
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/Codec.java b/ring-android/src/cx/ring/model/Codec.java
new file mode 100644
index 0000000..0bef48f
--- /dev/null
+++ b/ring-android/src/cx/ring/model/Codec.java
@@ -0,0 +1,147 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import cx.ring.service.StringVect;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Codec implements Parcelable {
+    int payload;
+    String name;
+    String sampleRate;
+    String bitRate;
+    String channels;
+    boolean enabled;
+
+    public Codec(int i, StringVect audioCodecDetails, boolean b) {
+        payload = i;
+        name = audioCodecDetails.get(0);
+        sampleRate = audioCodecDetails.get(1);
+        bitRate = audioCodecDetails.get(2);
+        channels = audioCodecDetails.get(3);
+        enabled = b;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(payload);
+        out.writeString(name);
+        out.writeString(sampleRate);
+        out.writeString(bitRate);
+        out.writeString(channels);
+        out.writeByte((byte) (enabled ? 1 : 0));
+    }
+
+    public static final Parcelable.Creator<Codec> CREATOR = new Parcelable.Creator<Codec>() {
+        public Codec createFromParcel(Parcel in) {
+            return new Codec(in);
+        }
+
+        public Codec[] newArray(int size) {
+            return new Codec[size];
+        }
+    };
+
+    private Codec(Parcel in) {
+        payload = in.readInt();
+        name = in.readString();
+        sampleRate = in.readString();
+        bitRate = in.readString();
+        channels = in.readString();
+        enabled = in.readByte() == 1;
+    }
+
+    public Codec(Codec c) {
+        payload = c.payload;
+        name = c.name;
+        sampleRate = c.sampleRate;
+        bitRate = c.bitRate;
+        channels = c.channels;
+        enabled = c.enabled;
+    }
+
+    @Override
+    public String toString() {
+        return "Codec: " + name + "\n" + "Payload: " + payload + "\n" + "Sample Rate: " + sampleRate + "\n" + "Bit Rate: " + bitRate + "\n"
+                + "Channels: " + channels;
+    }
+
+    public CharSequence getPayload() {
+        return Integer.toString(payload);
+    }
+
+    public CharSequence getName() {
+        return name;
+    }
+
+    public String getSampleRate() {
+        return sampleRate;
+    }
+
+    public String getBitRate() {
+        return bitRate;
+    }
+
+    public String getChannels() {
+        return channels;
+    }
+
+    public boolean isEnabled() {
+       return enabled;
+    }
+
+    public void setEnabled(boolean b) {
+        enabled = b;
+    }
+
+    public void toggleState() {
+        enabled = !enabled;
+        
+    }
+    
+    @Override
+    public boolean equals(Object o){
+        return o instanceof Codec && ((Codec) o).payload == payload;
+    }
+
+    public boolean isSpeex() {
+        return name.contentEquals("speex");
+    }   
+
+}
diff --git a/ring-android/src/cx/ring/model/Conference.aidl b/ring-android/src/cx/ring/model/Conference.aidl
new file mode 100644
index 0000000..774e946
--- /dev/null
+++ b/ring-android/src/cx/ring/model/Conference.aidl
@@ -0,0 +1,4 @@
+package cx.ring.model;
+
+
+parcelable Conference;
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/model/Conference.java b/ring-android/src/cx/ring/model/Conference.java
new file mode 100644
index 0000000..db027f6
--- /dev/null
+++ b/ring-android/src/cx/ring/model/Conference.java
@@ -0,0 +1,290 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import java.util.ArrayList;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Conference implements Parcelable {
+
+    private String id;
+    private int mConfState;
+    private ArrayList<SipCall> participants;
+    private boolean recording;
+    private ArrayList<SipMessage> messages;
+
+    public static String DEFAULT_ID = "-1";
+
+    public boolean isRinging() {
+        return participants.get(0).isRinging();
+    }
+
+    public void removeParticipant(SipCall toRemove) {
+        participants.remove(toRemove);
+    }
+
+    public boolean useSecureLayer() {
+        for(SipCall call : participants){
+            if(call.getAccount().useSecureLayer())
+                return true;
+        }
+        return false;
+    }
+
+    public interface state {
+        int ACTIVE_ATTACHED = 0;
+        int ACTIVE_DETACHED = 1;
+        int ACTIVE_ATTACHED_REC = 2;
+        int ACTIVE_DETACHED_REC = 3;
+        int HOLD = 4;
+        int HOLD_REC = 5;
+    }
+
+    public void setCallState(String callID, int newState) {
+        if(id.contentEquals(callID))
+            mConfState = newState;
+        else {
+            getCallById(callID).setCallState(newState);
+        }
+    }
+
+    public void setCallState(String confID, String newState) {
+        if (newState.equals("ACTIVE_ATTACHED")) {
+            setCallState(confID, state.ACTIVE_ATTACHED);
+        } else if (newState.equals("ACTIVE_DETACHED")) {
+            setCallState(confID, state.ACTIVE_DETACHED);
+        } else if (newState.equals("ACTIVE_ATTACHED_REC")) {
+            setCallState(confID, state.ACTIVE_ATTACHED_REC);
+        } else if (newState.equals("ACTIVE_DETACHED_REC")) {
+            setCallState(confID, state.ACTIVE_DETACHED_REC);
+        } else if (newState.equals("HOLD")) {
+            setCallState(confID, state.HOLD);
+        } else if (newState.equals("HOLD_REC")) {
+            setCallState(confID, state.HOLD_REC);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(id);
+        out.writeInt(mConfState);
+        ArrayList<SipCall> normal_calls = new ArrayList<SipCall>();
+        ArrayList<SecureSipCall> secure_calls = new ArrayList<SecureSipCall>();
+
+        for(SipCall part : participants){
+            if(part instanceof SecureSipCall)
+                secure_calls.add((SecureSipCall) part);
+            else
+                normal_calls.add(part);
+        }
+        out.writeTypedList(secure_calls);
+        out.writeTypedList(normal_calls);
+        out.writeByte((byte) (recording ? 1 : 0));
+        out.writeTypedList(messages);
+    }
+
+    public static final Parcelable.Creator<Conference> CREATOR = new Parcelable.Creator<Conference>() {
+        public Conference createFromParcel(Parcel in) {
+            return new Conference(in);
+        }
+
+        public Conference[] newArray(int size) {
+            return new Conference[size];
+        }
+    };
+
+
+    private Conference(Parcel in) {
+        participants = new ArrayList<SipCall>();
+        id = in.readString();
+        mConfState = in.readInt();
+        ArrayList<SecureSipCall> tmp = new ArrayList<SecureSipCall>();
+        in.readTypedList(tmp, SecureSipCall.CREATOR);
+        in.readTypedList(participants, SipCall.CREATOR);
+        participants.addAll(tmp);
+        recording = in.readByte() == 1;
+        messages = new ArrayList<SipMessage>();
+        in.readTypedList(messages, SipMessage.CREATOR);
+    }
+
+    public Conference(SipCall call) {
+        this(DEFAULT_ID);
+        participants.add(call);
+    }
+
+    public Conference(String cID) {
+        id = cID;
+        participants = new ArrayList<SipCall>();
+        recording = false;
+        messages = new ArrayList<SipMessage>();
+    }
+
+    public Conference(Conference c) {
+        id = c.id;
+        mConfState = c.mConfState;
+        participants = new ArrayList<SipCall>(c.participants);
+        recording = c.recording;
+        messages = new ArrayList<SipMessage>();
+    }
+
+    public String getId() {
+        if(hasMultipleParticipants())
+            return id;
+        else
+            return participants.get(0).getCallId();
+    }
+
+    public String getState() {
+        if (participants.size() == 1) {
+            return participants.get(0).getCallStateString();
+        }
+        return getConferenceStateString();
+    }
+
+    public String getConferenceStateString() {
+
+        String text_state;
+
+        switch (mConfState) {
+            case state.ACTIVE_ATTACHED:
+                text_state = "ACTIVE_ATTACHED";
+                break;
+            case state.ACTIVE_DETACHED:
+                text_state = "ACTIVE_DETACHED";
+                break;
+            case state.ACTIVE_ATTACHED_REC:
+                text_state = "ACTIVE_ATTACHED_REC";
+                break;
+            case state.ACTIVE_DETACHED_REC:
+                text_state = "ACTIVE_DETACHED_REC";
+                break;
+            case state.HOLD:
+                text_state = "HOLD";
+                break;
+            case state.HOLD_REC:
+                text_state = "HOLD_REC";
+                break;
+            default:
+                text_state = "NULL";
+        }
+
+        return text_state;
+    }
+
+    public ArrayList<SipCall> getParticipants() {
+        return participants;
+    }
+
+    public boolean contains(String callID) {
+        for (SipCall participant : participants) {
+            if (participant.getCallId().contentEquals(callID))
+                return true;
+        }
+        return false;
+    }
+
+    public SipCall getCallById(String callID) {
+        for (SipCall participant : participants) {
+            if (participant.getCallId().contentEquals(callID))
+                return participant;
+        }
+        return null;
+    }
+
+    /**
+     * Compare conferences based on confID/participants
+     */
+    @Override
+    public boolean equals(Object c) {
+        if (c instanceof Conference) {
+            if (((Conference) c).id.contentEquals(id) && !id.contentEquals("-1")) {
+                return true;
+            } else {
+                if (((Conference) c).id.contentEquals(id)) {
+                    for (SipCall participant : participants) {
+                        if (!((Conference) c).contains(participant.getCallId()))
+                            return false;
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+
+    }
+
+    public boolean hasMultipleParticipants() {
+        return participants.size() > 1;
+    }
+
+    public boolean isOnHold() {
+        return participants.size() == 1 && participants.get(0).isOnHold() || getConferenceStateString().contentEquals("HOLD");
+    }
+
+    public boolean isIncoming() {
+        return participants.size() == 1 && participants.get(0).isIncoming();
+    }
+
+
+    public void setRecording(boolean b) {
+        recording = b;
+    }
+
+    public boolean isRecording() {
+            return recording;
+    }
+
+
+    public boolean isOnGoing() {
+        return participants.size() == 1 && participants.get(0).isOngoing() || participants.size() > 1;
+    }
+
+    public ArrayList<SipMessage> getMessages() {
+        return messages;
+    }
+
+    public void addSipMessage(SipMessage sipMessage) {
+        messages.add(sipMessage);
+    }
+
+    public void addParticipant(SipCall part) {
+        participants.add(part);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/SecureSipCall.java b/ring-android/src/cx/ring/model/SecureSipCall.java
new file mode 100644
index 0000000..5f5e729
--- /dev/null
+++ b/ring-android/src/cx/ring/model/SecureSipCall.java
@@ -0,0 +1,201 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux>
+ *
+ *  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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import cx.ring.model.account.AccountDetailSrtp;
+
+
+public class SecureSipCall extends SipCall {
+
+    public interface SecureLayer {
+        int ZRTP_LAYER = 0;
+        int SDES_LAYER = 1;
+    }
+
+    public final static int DISPLAY_GREEN_LOCK = 0;
+    public final static int DISPLAY_RED_LOCK = 1;
+    public final static int DISPLAY_CONFIRM_SAS = 2;
+    public final static int DISPLAY_NONE = 3;
+
+    int mSecureLayerUsed;
+    ZrtpModule mZrtpModule;
+    SdesModule mSdesModule;
+
+    private boolean isInitialized;
+
+    public SecureSipCall(SipCall call) {
+        super(call);
+        isInitialized = false;
+        String keyExchange = getAccount().getSrtpDetails().getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE);
+        if (keyExchange.contentEquals("zrtp")) {
+            mSecureLayerUsed = SecureLayer.ZRTP_LAYER;
+        } else if (keyExchange.contentEquals("sdes")) {
+            mSecureLayerUsed = SecureLayer.SDES_LAYER;
+        }
+
+        mZrtpModule = new ZrtpModule();
+        mSdesModule = new SdesModule();
+    }
+
+    public void setSASConfirmed(boolean confirmedSAS) {
+        mZrtpModule.needSASConfirmation = !confirmedSAS;
+    }
+
+    public String getSAS() {
+        return mZrtpModule.SAS;
+    }
+
+    public void setSAS(String SAS) {
+        mZrtpModule.SAS = SAS;
+    }
+
+    public SecureSipCall(Parcel in) {
+        super(in);
+        isInitialized = in.readByte() == 1;
+        mSecureLayerUsed = in.readInt();
+        mSdesModule = new SdesModule(in);
+        mZrtpModule = new ZrtpModule(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeByte((byte) (isInitialized ? 1 : 0));
+        out.writeInt(mSecureLayerUsed);
+        mSdesModule.writeToParcel(out);
+        mZrtpModule.writeToParcel(out);
+    }
+
+    public static final Parcelable.Creator<SecureSipCall> CREATOR = new Parcelable.Creator<SecureSipCall>() {
+        public SecureSipCall createFromParcel(Parcel in) {
+            return new SecureSipCall(in);
+        }
+
+        public SecureSipCall[] newArray(int size) {
+            return new SecureSipCall[size];
+        }
+    };
+
+    public void sasConfirmedByZrtpLayer(int verified) {
+        // Not used
+    }
+
+    public void setZrtpSupport(boolean support) {
+        mZrtpModule.zrtpIsSupported = support;
+        if (!support)
+            mZrtpModule.needSASConfirmation = false;
+    }
+
+    public void setInitialized() {
+        isInitialized = true;
+    }
+
+    /*
+    * returns what state should be visible during call
+    */
+    public int displayModule() {
+        if (isInitialized) {
+            Log.i("SecureSIp", "needSASConfirmation" + mZrtpModule.needSASConfirmation);
+            if (mZrtpModule.needSASConfirmation) {
+                return DISPLAY_CONFIRM_SAS;
+            } else if (mZrtpModule.zrtpIsSupported || mSdesModule.sdesIsOn) {
+                return DISPLAY_GREEN_LOCK;
+            } else {
+                return DISPLAY_RED_LOCK;
+            }
+        }
+        return DISPLAY_NONE;
+    }
+
+    public void useSecureSDES(boolean use) {
+        mSdesModule.sdesIsOn = use;
+        mZrtpModule.needSASConfirmation = false;
+    }
+
+
+    private class ZrtpModule {
+        private String SAS;
+        private boolean needSASConfirmation;
+        private boolean zrtpIsSupported;
+
+        // static preferences of account
+        private final boolean displaySas;
+        private final boolean alertIfZrtpNotSupported;
+        private final boolean displaySASOnHold;
+
+        public ZrtpModule() {
+            displaySas = getAccount().getSrtpDetails().getDetailBoolean(AccountDetailSrtp.CONFIG_ZRTP_DISPLAY_SAS);
+            alertIfZrtpNotSupported = getAccount().getSrtpDetails().getDetailBoolean(AccountDetailSrtp.CONFIG_ZRTP_NOT_SUPP_WARNING);
+            displaySASOnHold = getAccount().getSrtpDetails().getDetailBoolean(AccountDetailSrtp.CONFIG_ZRTP_NOT_SUPP_WARNING);
+            needSASConfirmation = displaySas;
+            zrtpIsSupported = false;
+        }
+
+        public ZrtpModule(Parcel in) {
+            SAS = in.readString();
+            displaySas = in.readByte() == 1;
+            alertIfZrtpNotSupported = in.readByte() == 1;
+            displaySASOnHold = in.readByte() == 1;
+            zrtpIsSupported = in.readByte() == 1;
+            needSASConfirmation = in.readByte() == 1;
+        }
+
+        public void writeToParcel(Parcel dest) {
+            dest.writeString(SAS);
+            dest.writeByte((byte) (displaySas ? 1 : 0));
+            dest.writeByte((byte) (alertIfZrtpNotSupported ? 1 : 0));
+            dest.writeByte((byte) (displaySASOnHold ? 1 : 0));
+            dest.writeByte((byte) (zrtpIsSupported ? 1 : 0));
+            dest.writeByte((byte) (needSASConfirmation ? 1 : 0));
+        }
+    }
+
+    private class SdesModule {
+
+        private boolean sdesIsOn;
+
+        public SdesModule() {
+            sdesIsOn = false;
+        }
+
+        public SdesModule(Parcel in) {
+            sdesIsOn = in.readByte() == 1;
+        }
+
+        public void writeToParcel(Parcel dest) {
+            dest.writeByte((byte) (sdesIsOn ? 1 : 0));
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/model/SipCall.aidl b/ring-android/src/cx/ring/model/SipCall.aidl
new file mode 100644
index 0000000..13c290c
--- /dev/null
+++ b/ring-android/src/cx/ring/model/SipCall.aidl
@@ -0,0 +1,4 @@
+package cx.ring.model;
+
+
+parcelable SipCall;
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/model/SipCall.java b/ring-android/src/cx/ring/model/SipCall.java
new file mode 100644
index 0000000..f64a505
--- /dev/null
+++ b/ring-android/src/cx/ring/model/SipCall.java
@@ -0,0 +1,298 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux>
+ *          Alexandre Savard <alexandre.savard@gmail.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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.model;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import cx.ring.model.account.Account;
+
+public class SipCall implements Parcelable {
+
+    public static String ID = "id";
+    public static String ACCOUNT = "account";
+    public static String CONTACT = "contact";
+    public static String TYPE = "type";
+    public static String STATE = "state";
+
+    private static final String TAG = SipCall.class.getSimpleName();
+
+    private String mCallID = "";
+    private Account mAccount = null;
+    private CallContact mContact = null;
+    private boolean isRecording = false;
+    private long timestampStart_ = 0;
+    private long timestampEnd_ = 0;
+
+    private int mCallType;
+    private int mCallState = state.CALL_STATE_NONE;
+
+    public SipCall(SipCall call) {
+        mCallID = call.mCallID;
+        mAccount = call.mAccount;
+        mContact = call.mContact;
+        isRecording = call.isRecording;
+        timestampStart_ = call.timestampStart_;
+        timestampEnd_ = call.timestampEnd_;
+        mCallType = call.mCallType;
+        mCallState = call.mCallState;
+    }
+
+    /**
+     * *********************
+     * Construtors
+     * *********************
+     */
+
+    protected SipCall(Parcel in) {
+
+        mCallID = in.readString();
+        mAccount = in.readParcelable(Account.class.getClassLoader());
+        mContact = in.readParcelable(CallContact.class.getClassLoader());
+        isRecording = in.readByte() == 1;
+        mCallType = in.readInt();
+        mCallState = in.readInt();
+        timestampStart_ = in.readLong();
+        timestampEnd_ = in.readLong();
+    }
+
+    public SipCall(Bundle args) {
+        mCallID = args.getString(ID);
+        mAccount = args.getParcelable(ACCOUNT);
+        mCallType = args.getInt(TYPE);
+        mCallState = args.getInt(STATE);
+        mContact = args.getParcelable(CONTACT);
+    }
+
+    public long getTimestampEnd_() {
+        return timestampEnd_;
+    }
+
+    public String getRecordPath() {
+        return "";
+    }
+
+    public int getCallType() {
+        return mCallType;
+    }
+
+    public Bundle getBundle() {
+        Bundle args = new Bundle();
+        args.putString(SipCall.ID, mCallID);
+        args.putParcelable(SipCall.ACCOUNT, mAccount);
+        args.putInt(SipCall.STATE, mCallState);
+        args.putInt(SipCall.TYPE, mCallType);
+        args.putParcelable(SipCall.CONTACT, mContact);
+        return args;
+    }
+
+
+    public interface direction {
+        public static final int CALL_TYPE_INCOMING = 1;
+        public static final int CALL_TYPE_OUTGOING = 2;
+    }
+
+    public interface state {
+        public static final int CALL_STATE_NONE = 0;
+        public static final int CALL_STATE_RINGING = 2;
+        public static final int CALL_STATE_CURRENT = 3;
+        public static final int CALL_STATE_HUNGUP = 4;
+        public static final int CALL_STATE_BUSY = 5;
+        public static final int CALL_STATE_FAILURE = 6;
+        public static final int CALL_STATE_HOLD = 7;
+        public static final int CALL_STATE_UNHOLD = 8;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+
+        out.writeString(mCallID);
+        out.writeParcelable(mAccount, 0);
+
+        out.writeParcelable(mContact, 0);
+        out.writeByte((byte) (isRecording ? 1 : 0));
+        out.writeInt(mCallType);
+        out.writeInt(mCallState);
+        out.writeLong(timestampStart_);
+        out.writeLong(timestampEnd_);
+    }
+
+    public static final Parcelable.Creator<SipCall> CREATOR = new Parcelable.Creator<SipCall>() {
+        public SipCall createFromParcel(Parcel in) {
+            return new SipCall(in);
+        }
+
+        public SipCall[] newArray(int size) {
+            return new SipCall[size];
+        }
+    };
+
+    public void setCallID(String callID) {
+        mCallID = callID;
+    }
+
+    public String getCallId() {
+        return mCallID;
+    }
+
+    public long getTimestampStart_() {
+        return timestampStart_;
+    }
+
+    public void setTimestampStart_(long timestampStart_) {
+        this.timestampStart_ = timestampStart_;
+    }
+
+    public void setTimestampEnd_(long timestampEnd_) {
+        this.timestampEnd_ = timestampEnd_;
+    }
+
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    public Account getAccount() {
+        return mAccount;
+    }
+
+    public String getCallTypeString() {
+        switch (mCallType) {
+            case direction.CALL_TYPE_INCOMING:
+                return "CALL_TYPE_INCOMING";
+            case direction.CALL_TYPE_OUTGOING:
+                return "CALL_TYPE_OUTGOING";
+            default:
+                return "CALL_TYPE_UNDETERMINED";
+        }
+    }
+
+    public void setCallState(int callState) {
+        mCallState = callState;
+    }
+
+    public CallContact getmContact() {
+        return mContact;
+    }
+
+    public String getCallStateString() {
+
+        String text_state;
+
+        switch (mCallState) {
+            case state.CALL_STATE_NONE:
+                text_state = "NONE";
+                break;
+            case state.CALL_STATE_RINGING:
+                text_state = "RINGING";
+                break;
+            case state.CALL_STATE_CURRENT:
+                text_state = "CURRENT";
+                break;
+            case state.CALL_STATE_HUNGUP:
+                text_state = "HUNGUP";
+                break;
+            case state.CALL_STATE_BUSY:
+                text_state = "BUSY";
+                break;
+            case state.CALL_STATE_FAILURE:
+                text_state = "FAILURE";
+                break;
+            case state.CALL_STATE_HOLD:
+                text_state = "HOLD";
+                break;
+            case state.CALL_STATE_UNHOLD:
+                text_state = "UNHOLD";
+                break;
+            default:
+                text_state = "NULL";
+        }
+
+        return text_state;
+    }
+
+    public boolean isRecording() {
+        return isRecording;
+    }
+
+    public void setRecording(boolean isRecording) {
+        this.isRecording = isRecording;
+    }
+
+    public void printCallInfo() {
+        Log.i(TAG, "CallInfo: CallID: " + mCallID);
+        Log.i(TAG, "          AccountID: " + mAccount.getAccountID());
+        Log.i(TAG, "          CallState: " + mCallState);
+        Log.i(TAG, "          CallType: " + mCallType);
+    }
+
+    /**
+     * Compare sip calls based on call ID
+     */
+    @Override
+    public boolean equals(Object c) {
+        return c instanceof SipCall && ((SipCall) c).mCallID.contentEquals((mCallID));
+    }
+
+    public boolean isOutGoing() {
+        return mCallType == direction.CALL_TYPE_OUTGOING;
+    }
+
+    public boolean isRinging() {
+        return mCallState == state.CALL_STATE_RINGING || mCallState == state.CALL_STATE_NONE;
+    }
+
+    public boolean isIncoming() {
+        return mCallType == direction.CALL_TYPE_INCOMING;
+    }
+
+    public boolean isOngoing() {
+        return !(mCallState == state.CALL_STATE_RINGING || mCallState == state.CALL_STATE_NONE || mCallState == state.CALL_STATE_FAILURE
+                || mCallState == state.CALL_STATE_BUSY || mCallState == state.CALL_STATE_HUNGUP);
+
+    }
+
+    public boolean isOnHold() {
+        return mCallState == state.CALL_STATE_HOLD;
+    }
+
+    public boolean isCurrent() {
+        return mCallState == state.CALL_STATE_CURRENT;
+    }
+
+
+}
diff --git a/ring-android/src/cx/ring/model/SipMessage.aidl b/ring-android/src/cx/ring/model/SipMessage.aidl
new file mode 100644
index 0000000..0729146
--- /dev/null
+++ b/ring-android/src/cx/ring/model/SipMessage.aidl
@@ -0,0 +1,4 @@
+package cx.ring.model;
+
+
+parcelable SipMessage;
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/model/SipMessage.java b/ring-android/src/cx/ring/model/SipMessage.java
new file mode 100644
index 0000000..f8a43cc
--- /dev/null
+++ b/ring-android/src/cx/ring/model/SipMessage.java
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class SipMessage implements Parcelable {
+    public boolean left;
+    public String comment;
+
+    public SipMessage(boolean left, String comment) {
+        super();
+        this.left = left;
+        this.comment = comment;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByte((byte) (left ? 1 : 0));
+        out.writeString(comment);
+    }
+
+    public static final Parcelable.Creator<SipMessage> CREATOR = new Parcelable.Creator<SipMessage>() {
+        public SipMessage createFromParcel(Parcel in) {
+            return new SipMessage(in);
+        }
+
+        public SipMessage[] newArray(int size) {
+            return new SipMessage[size];
+        }
+    };
+
+    private SipMessage(Parcel in) {
+        left = (in.readByte() == 1);
+        comment = in.readString();
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/model/account/Account.java b/ring-android/src/cx/ring/model/account/Account.java
new file mode 100644
index 0000000..c129dde
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/Account.java
@@ -0,0 +1,244 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Account extends java.util.Observable implements Parcelable {
+
+    String accountID;
+    private AccountDetailBasic basicDetails = null;
+    private AccountDetailAdvanced advancedDetails = null;
+    private AccountDetailSrtp srtpDetails = null;
+    private AccountDetailTls tlsDetails = null;
+    private ArrayList<AccountCredentials> credentialsDetails;
+
+    public Account(String bAccountID, HashMap<String, String> details, ArrayList<HashMap<String, String>> credentials) {
+        accountID = bAccountID;
+        basicDetails = new AccountDetailBasic(details);
+        advancedDetails = new AccountDetailAdvanced(details);
+        srtpDetails = new AccountDetailSrtp(details);
+        tlsDetails = new AccountDetailTls(details);
+        credentialsDetails = new ArrayList<AccountCredentials>();
+        for (int i = 0; i < credentials.size(); ++i) {
+            credentialsDetails.add(new AccountCredentials(credentials.get(i)));
+        }
+    }
+
+    public String getAccountID() {
+        return accountID;
+    }
+
+    public void setAccountID(String accountID) {
+        this.accountID = accountID;
+    }
+
+    public String getHost() {
+        return basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME);
+    }
+
+    public void setHost(String host) {
+        basicDetails.setDetailString(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME, host);
+    }
+
+    public String getRegistered_state() {
+        return advancedDetails.getDetailString(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATUS);
+    }
+
+    public void setRegistered_state(String registered_state) {
+        advancedDetails.setDetailString(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATUS, registered_state);
+    }
+
+    public String getAlias() {
+        return basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS);
+    }
+
+	public Boolean isSip() {
+		return basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_TYPE).equals("SIP");
+	}
+
+    public void setAlias(String alias) {
+        basicDetails.setDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS, alias);
+    }
+
+    public Account(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int arg1) {
+
+        dest.writeString(accountID);
+        dest.writeSerializable(basicDetails.getDetailsHashMap());
+        dest.writeSerializable(advancedDetails.getDetailsHashMap());
+        dest.writeSerializable(srtpDetails.getDetailsHashMap());
+        dest.writeSerializable(tlsDetails.getDetailsHashMap());
+        dest.writeInt(credentialsDetails.size());
+        for (AccountCredentials cred : credentialsDetails) {
+            dest.writeSerializable(cred.getDetailsHashMap());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void readFromParcel(Parcel in) {
+
+        accountID = in.readString();
+        basicDetails = new AccountDetailBasic((HashMap<String, String>) in.readSerializable());
+        advancedDetails = new AccountDetailAdvanced((HashMap<String, String>) in.readSerializable());
+        srtpDetails = new AccountDetailSrtp((HashMap<String, String>) in.readSerializable());
+        tlsDetails = new AccountDetailTls((HashMap<String, String>) in.readSerializable());
+        credentialsDetails = new ArrayList<AccountCredentials>();
+        int cred_count = in.readInt();
+        for (int i = 0; i < cred_count; ++i) {
+            credentialsDetails.add(new AccountCredentials((HashMap<String, String>) in.readSerializable()));
+        }
+    }
+
+    public static final Parcelable.Creator<Account> CREATOR = new Parcelable.Creator<Account>() {
+        @Override
+        public Account createFromParcel(Parcel in) {
+            return new Account(in);
+        }
+
+        @Override
+        public Account[] newArray(int size) {
+            return new Account[size];
+        }
+    };
+
+    public AccountDetailBasic getBasicDetails() {
+        return basicDetails;
+    }
+
+    public void setBasicDetails(AccountDetailBasic basicDetails) {
+        this.basicDetails = basicDetails;
+    }
+
+    public AccountDetailAdvanced getAdvancedDetails() {
+        return advancedDetails;
+    }
+
+    public void setAdvancedDetails(AccountDetailAdvanced advancedDetails) {
+        this.advancedDetails = advancedDetails;
+    }
+
+    public AccountDetailSrtp getSrtpDetails() {
+        return srtpDetails;
+    }
+
+    public void setSrtpDetails(AccountDetailSrtp srtpDetails) {
+        this.srtpDetails = srtpDetails;
+    }
+
+    public AccountDetailTls getTlsDetails() {
+        return tlsDetails;
+    }
+
+    public void setTlsDetails(AccountDetailTls tlsDetails) {
+        this.tlsDetails = tlsDetails;
+    }
+
+    public boolean isEnabled() {
+        return (basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ENABLE).contentEquals(AccountDetail.TRUE_STR));
+    }
+
+    public void setEnabled(boolean isChecked) {
+        basicDetails.setDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ENABLE, (isChecked ? AccountDetail.TRUE_STR
+                : AccountDetail.FALSE_STR));
+    }
+
+    public HashMap<String, String> getDetails() {
+        HashMap<String, String> results = new HashMap<String, String>();
+
+        results.putAll(basicDetails.getDetailsHashMap());
+        results.putAll(advancedDetails.getDetailsHashMap());
+        results.putAll(tlsDetails.getDetailsHashMap());
+        results.putAll(srtpDetails.getDetailsHashMap());
+        return results;
+    }
+
+    public boolean isRegistered() {
+        // FIXME Hardcoded values
+        return (getRegistered_state().contentEquals("REGISTERED") || getRegistered_state().contentEquals("OK"));
+    }
+
+    public boolean isIP2IP() {
+        return basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS).contentEquals("IP2IP");
+    }
+
+    public boolean isAutoanswerEnabled() {
+        return basicDetails.getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_AUTOANSWER).contentEquals("true");
+    }
+
+    public ArrayList<AccountCredentials> getCredentials() {
+        return credentialsDetails;
+    }
+
+    public void addCredential(AccountCredentials newValue) {
+        credentialsDetails.add(newValue);
+    }
+
+    public void removeCredential(AccountCredentials accountCredentials) {
+        credentialsDetails.remove(accountCredentials);
+    }
+
+    @Override
+    public boolean hasChanged() {
+        return true;
+    }
+
+    public List getCredentialsHashMapList() {
+        ArrayList<HashMap<String, String>> result = new ArrayList<HashMap<String, String>>();
+        for (AccountCredentials cred : credentialsDetails) {
+            result.add(cred.getDetailsHashMap());
+        }
+        return result;
+    }
+
+    public boolean hasSDESEnabled() {
+        return srtpDetails.getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE).contentEquals("sdes");
+    }
+
+    public boolean useSecureLayer() {
+        return getSrtpDetails().getDetailBoolean(AccountDetailSrtp.CONFIG_SRTP_ENABLE) || getTlsDetails().getDetailBoolean(AccountDetailTls.CONFIG_TLS_ENABLE);
+    }
+}
diff --git a/ring-android/src/cx/ring/model/account/AccountCredentials.java b/ring-android/src/cx/ring/model/account/AccountCredentials.java
new file mode 100644
index 0000000..bd0eebb
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountCredentials.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class AccountCredentials implements AccountDetail {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = AccountCredentials.class.getSimpleName();
+
+    public static final String CONFIG_ACCOUNT_USERNAME = "Account.username";
+    public static final String CONFIG_ACCOUNT_PASSWORD = "Account.password";
+    public static final String CONFIG_ACCOUNT_REALM = "Account.realm";
+
+    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+
+    public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() {
+        ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<AccountDetail.PreferenceEntry>();
+
+        preference.add(new PreferenceEntry(CONFIG_ACCOUNT_USERNAME));
+        preference.add(new PreferenceEntry(CONFIG_ACCOUNT_PASSWORD));
+        preference.add(new PreferenceEntry(CONFIG_ACCOUNT_REALM));
+
+        return preference;
+    }
+
+    public AccountCredentials(HashMap<String, String> pref) {
+        privateArray = getPreferenceEntries();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            p.mValue = pref.get(p.mKey);
+        }
+
+    }
+
+    public ArrayList<AccountDetail.PreferenceEntry> getDetailValues() {
+        return privateArray;
+    }
+
+    public ArrayList<String> getValuesOnly() {
+        ArrayList<String> valueList = new ArrayList<String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            valueList.add(p.mValue);
+        }
+
+        return valueList;
+    }
+
+    public HashMap<String, String> getDetailsHashMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            map.put(p.mKey, p.mValue);
+        }
+
+        return map;
+    }
+
+    public String getDetailString(String key) {
+        String value = "";
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                value = p.mValue;
+                return value;
+            }
+        }
+        return value;
+    }
+
+    public void setDetailString(String key, String newValue) {
+        for (int i = 0; i < privateArray.size(); ++i) {
+            PreferenceEntry p = privateArray.get(i);
+            if (p.mKey.equals(key)) {
+                privateArray.get(i).mValue = newValue;
+            }
+        }
+
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof AccountCredentials)
+            return ((AccountCredentials) other).getDetailsHashMap().get(CONFIG_ACCOUNT_USERNAME)
+                    .contentEquals(getDetailString(CONFIG_ACCOUNT_USERNAME))
+                    && ((AccountCredentials) other).getDetailsHashMap().get(CONFIG_ACCOUNT_PASSWORD)
+                            .contentEquals(getDetailString(CONFIG_ACCOUNT_PASSWORD))
+                    && ((AccountCredentials) other).getDetailsHashMap().get(CONFIG_ACCOUNT_REALM)
+                            .contentEquals(getDetailString(CONFIG_ACCOUNT_REALM));
+
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/model/account/AccountDetail.java b/ring-android/src/cx/ring/model/account/AccountDetail.java
new file mode 100644
index 0000000..54fce72
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountDetail.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public interface AccountDetail {
+
+    public static final String TRUE_STR = "true";
+    public static final String FALSE_STR = "false";
+
+    public static class PreferenceEntry {
+        public String mKey;
+        public boolean isTwoState;
+        public String mValue;
+
+        public PreferenceEntry(String key) {
+            mKey = key;
+            isTwoState = false;
+            mValue = "";
+        }
+
+        public PreferenceEntry(String key, boolean twoState) {
+            mKey = key;
+            isTwoState = twoState;
+            mValue = "";
+        }
+
+        public boolean isChecked() {
+            return mValue.contentEquals("true");
+        }
+    }
+
+    public static final String TAG = "PreferenceHashMap";
+
+    public ArrayList<PreferenceEntry> getDetailValues();
+
+    public ArrayList<String> getValuesOnly();
+
+    public HashMap<String, String> getDetailsHashMap();
+
+    public String getDetailString(String key);
+
+    public void setDetailString(String key, String newValue);
+
+}
diff --git a/ring-android/src/cx/ring/model/account/AccountDetailAdvanced.java b/ring-android/src/cx/ring/model/account/AccountDetailAdvanced.java
new file mode 100644
index 0000000..2d09ba5
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountDetailAdvanced.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.util.Log;
+
+public class AccountDetailAdvanced implements AccountDetail {
+
+    private static final String TAG = "AccountDetailAdvanced";
+
+    public static final String CONFIG_ACCOUNT_MAILBOX = "Account.mailbox";
+    public static final String CONFIG_ACCOUNT_REGISTRATION_EXPIRE = "Account.registrationExpire";
+    public static final String CONFIG_ACCOUNT_REGISTRATION_STATUS = "Account.registrationStatus";
+    public static final String CONFIG_ACCOUNT_REGISTRATION_STATE_CODE = "Account.registrationCode";
+    public static final String CONFIG_ACCOUNT_REGISTRATION_STATE_DESC = "Account.registrationDescription";
+    public static final String CONFIG_CREDENTIAL_NUMBER = "Credential.count";
+    public static final String CONFIG_ACCOUNT_DTMF_TYPE = "Account.dtmfType";
+    public static final String CONFIG_RINGTONE_PATH = "Account.ringtonePath";
+    public static final String CONFIG_RINGTONE_ENABLED = "Account.ringtoneEnabled";
+    public static final String CONFIG_KEEP_ALIVE_ENABLED = "Account.keepAliveEnabled";
+
+    public static final String CONFIG_LOCAL_INTERFACE = "Account.localInterface";
+    public static final String CONFIG_PUBLISHED_SAMEAS_LOCAL = "Account.publishedSameAsLocal";
+    public static final String CONFIG_LOCAL_PORT = "Account.localPort";
+    public static final String CONFIG_PUBLISHED_PORT = "Account.publishedPort";
+    public static final String CONFIG_PUBLISHED_ADDRESS = "Account.publishedAddress";
+
+    public static final String CONFIG_STUN_SERVER = "STUN.server";
+    public static final String CONFIG_STUN_ENABLE = "STUN.enable";
+
+    public static final String CONFIG_AUDIO_PORT_MIN = "Account.audioPortMin";
+    public static final String CONFIG_AUDIO_PORT_MAX = "Account.audioPortMax";
+
+    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+
+    public AccountDetailAdvanced(HashMap<String, String> pref) {
+        privateArray = new ArrayList<AccountDetail.PreferenceEntry>();
+
+        for (String key : pref.keySet()) {
+            PreferenceEntry p = new PreferenceEntry(key);
+            p.mValue = pref.get(key);
+
+            if(key.contentEquals(CONFIG_RINGTONE_ENABLED) ||
+                    key.contentEquals(CONFIG_KEEP_ALIVE_ENABLED) ||
+                    key.contentEquals(CONFIG_PUBLISHED_SAMEAS_LOCAL) ||
+                    key.contentEquals(CONFIG_STUN_ENABLE))
+                p.isTwoState = true;
+
+            privateArray.add(p);
+        }
+    }
+
+    public ArrayList<AccountDetail.PreferenceEntry> getDetailValues() {
+        return privateArray;
+    }
+
+    public ArrayList<String> getValuesOnly() {
+        ArrayList<String> valueList = new ArrayList<String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            Log.i(TAG, "" + p.mValue);
+            valueList.add(p.mValue);
+        }
+
+        return valueList;
+    }
+
+    public HashMap<String, String> getDetailsHashMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            map.put(p.mKey, p.mValue);
+        }
+
+        return map;
+    }
+
+    public String getDetailString(String key) {
+        String value = "";
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                value = p.mValue;
+                return value;
+            }
+        }
+        return value;
+    }
+
+    public void setDetailString(String key, String newValue) {
+        for (PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                p.mValue = newValue;
+            }
+        }
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/account/AccountDetailBasic.java b/ring-android/src/cx/ring/model/account/AccountDetailBasic.java
new file mode 100644
index 0000000..bec769e
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountDetailBasic.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.util.Log;
+
+public class AccountDetailBasic implements AccountDetail {
+
+    private static final String TAG = AccountDetailBasic.class.getSimpleName();
+
+    public static final String CONFIG_ACCOUNT_ALIAS = "Account.alias";
+    public static final String CONFIG_ACCOUNT_HOSTNAME = "Account.hostname";
+    public static final String CONFIG_ACCOUNT_USERNAME = "Account.username";
+    public static final String CONFIG_ACCOUNT_PASSWORD = "Account.password";
+
+    public static final String CONFIG_ACCOUNT_USERAGENT = "Account.useragent";
+    public static final String CONFIG_ACCOUNT_ROUTESET = "Account.routeset";
+    public static final String CONFIG_ACCOUNT_AUTOANSWER = "Account.autoAnswer";
+
+    public static final String CONFIG_ACCOUNT_REALM = "Account.realm";
+    public static final String CONFIG_ACCOUNT_TYPE = "Account.type";
+    public static final String CONFIG_ACCOUNT_ENABLE = "Account.enable";
+    public static final String CONFIG_PRESENCE_ENABLE = "Account.presenceEnabled";
+
+    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+
+    public AccountDetailBasic(HashMap<String, String> pref) {
+        privateArray = new ArrayList<AccountDetail.PreferenceEntry>();
+
+        for (String key : pref.keySet()) {
+            PreferenceEntry p = new PreferenceEntry(key);
+            p.mValue = pref.get(key);
+
+            if(key.contentEquals(CONFIG_ACCOUNT_ENABLE) || key.contentEquals(CONFIG_ACCOUNT_AUTOANSWER))
+                p.isTwoState = true;
+
+            privateArray.add(p);
+        }
+    }
+
+    public ArrayList<AccountDetail.PreferenceEntry> getDetailValues() {
+        return privateArray;
+    }
+
+    public ArrayList<String> getValuesOnly() {
+        ArrayList<String> valueList = new ArrayList<String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            Log.i(TAG, "" + p.mValue);
+            valueList.add(p.mValue);
+        }
+
+        return valueList;
+    }
+
+    public HashMap<String, String> getDetailsHashMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            map.put(p.mKey, p.mValue);
+        }
+
+        return map;
+    }
+
+    public String getDetailString(String key) {
+        String value = "";
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                value = p.mValue;
+                return value;
+            }
+        }
+
+        return value;
+    }
+
+    public void setDetailString(String key, String newValue) {
+        for (int i = 0; i < privateArray.size(); ++i) {
+            PreferenceEntry p = privateArray.get(i);
+            if (p.mKey.equals(key)) {
+                privateArray.get(i).mValue = newValue;
+            }
+        }
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/model/account/AccountDetailSrtp.java b/ring-android/src/cx/ring/model/account/AccountDetailSrtp.java
new file mode 100644
index 0000000..29b3ef5
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountDetailSrtp.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class AccountDetailSrtp implements AccountDetail {
+
+    private static final String TAG = "AccountDetailSrtp";
+
+    public static final String CONFIG_SRTP_ENABLE = "SRTP.enable";
+    public static final String CONFIG_SRTP_KEY_EXCHANGE = "SRTP.keyExchange";
+    public static final String CONFIG_SRTP_ENCRYPTION_ALGO = "SRTP.encryptionAlgorithm"; // Provided by ccRTP,0=NULL,1=AESCM,2=AESF8
+    public static final String CONFIG_SRTP_RTP_FALLBACK = "SRTP.rtpFallback";
+    public static final String CONFIG_ZRTP_HELLO_HASH = "ZRTP.helloHashEnable";
+    public static final String CONFIG_ZRTP_DISPLAY_SAS = "ZRTP.displaySAS";
+    public static final String CONFIG_ZRTP_NOT_SUPP_WARNING = "ZRTP.notSuppWarning";
+    public static final String CONFIG_ZRTP_DISPLAY_SAS_ONCE = "ZRTP.displaySasOnce";
+
+    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+
+    public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() {
+        ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<AccountDetail.PreferenceEntry>();
+
+        preference.add(new PreferenceEntry(CONFIG_SRTP_ENABLE, true));
+        preference.add(new PreferenceEntry(CONFIG_SRTP_KEY_EXCHANGE, false));
+        preference.add(new PreferenceEntry(CONFIG_SRTP_ENCRYPTION_ALGO, true));
+        preference.add(new PreferenceEntry(CONFIG_SRTP_RTP_FALLBACK, true));
+        preference.add(new PreferenceEntry(CONFIG_ZRTP_HELLO_HASH, true));
+        preference.add(new PreferenceEntry(CONFIG_ZRTP_DISPLAY_SAS, true));
+        preference.add(new PreferenceEntry(CONFIG_ZRTP_NOT_SUPP_WARNING, true));
+        preference.add(new PreferenceEntry(CONFIG_ZRTP_DISPLAY_SAS_ONCE, true));
+
+        return preference;
+    }
+
+    public AccountDetailSrtp(HashMap<String, String> pref) {
+        privateArray = getPreferenceEntries();
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            p.mValue = pref.get(p.mKey);
+        }
+    }
+
+    public ArrayList<AccountDetail.PreferenceEntry> getDetailValues() {
+        return privateArray;
+    }
+
+    public ArrayList<String> getValuesOnly() {
+        ArrayList<String> valueList = new ArrayList<String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            valueList.add(p.mValue);
+        }
+
+        return valueList;
+    }
+
+    public HashMap<String, String> getDetailsHashMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mValue == null) {
+                map.put(p.mKey, "");
+            } else {
+                map.put(p.mKey, p.mValue);
+            }
+        }
+
+        return map;
+    }
+
+    public String getDetailString(String key) {
+        String value = "";
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                value = p.mValue;
+                return value;
+            }
+        }
+
+        return value;
+    }
+
+    public void setDetailString(String key, String newValue) {
+        for (int i = 0; i < privateArray.size(); ++i) {
+            PreferenceEntry p = privateArray.get(i);
+            if (p.mKey.equals(key)) {
+                privateArray.get(i).mValue = newValue;
+            }
+        }
+
+    }
+
+    public boolean getDetailBoolean(String srtpParam) {
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(srtpParam)) {
+                return p.mValue.contentEquals("true");
+            }
+        }
+        return false;
+    }
+}
diff --git a/ring-android/src/cx/ring/model/account/AccountDetailTls.java b/ring-android/src/cx/ring/model/account/AccountDetailTls.java
new file mode 100644
index 0000000..37054b6
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/AccountDetailTls.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class AccountDetailTls implements AccountDetail {
+
+    private static final String TAG = "AccountDetailTls";
+
+    public static final String CONFIG_TLS_LISTENER_PORT = "TLS.listenerPort";
+    public static final String CONFIG_TLS_ENABLE = "TLS.enable";
+    public static final String CONFIG_TLS_CA_LIST_FILE = "TLS.certificateListFile";
+    public static final String CONFIG_TLS_CERTIFICATE_FILE = "TLS.certificateFile";
+    public static final String CONFIG_TLS_PRIVATE_KEY_FILE = "TLS.privateKeyFile";
+    public static final String CONFIG_TLS_PASSWORD = "TLS.password";
+    public static final String CONFIG_TLS_METHOD = "TLS.method";
+    public static final String CONFIG_TLS_CIPHERS = "TLS.ciphers";
+    public static final String CONFIG_TLS_SERVER_NAME = "TLS.serverName";
+    public static final String CONFIG_TLS_VERIFY_SERVER = "TLS.verifyServer";
+    public static final String CONFIG_TLS_VERIFY_CLIENT = "TLS.verifyClient";
+    public static final String CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE = "TLS.requireClientCertificate";
+    public static final String CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC = "TLS.negotiationTimeoutSec";
+
+    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+
+    public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() {
+        ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<AccountDetail.PreferenceEntry>();
+
+        preference.add(new PreferenceEntry(CONFIG_TLS_LISTENER_PORT));
+        preference.add(new PreferenceEntry(CONFIG_TLS_ENABLE, true));
+        preference.add(new PreferenceEntry(CONFIG_TLS_CA_LIST_FILE));
+        preference.add(new PreferenceEntry(CONFIG_TLS_CERTIFICATE_FILE));
+        preference.add(new PreferenceEntry(CONFIG_TLS_PRIVATE_KEY_FILE));
+        preference.add(new PreferenceEntry(CONFIG_TLS_PASSWORD));
+        preference.add(new PreferenceEntry(CONFIG_TLS_METHOD));
+        preference.add(new PreferenceEntry(CONFIG_TLS_CIPHERS));
+        preference.add(new PreferenceEntry(CONFIG_TLS_SERVER_NAME));
+        preference.add(new PreferenceEntry(CONFIG_TLS_VERIFY_SERVER));
+        preference.add(new PreferenceEntry(CONFIG_TLS_VERIFY_CLIENT, true));
+        preference.add(new PreferenceEntry(CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, true));
+        preference.add(new PreferenceEntry(CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC));
+
+        return preference;
+    }
+
+    public AccountDetailTls(HashMap<String, String> pref) {
+        privateArray = getPreferenceEntries();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            p.mValue = pref.get(p.mKey);
+        }
+    }
+
+    public ArrayList<AccountDetail.PreferenceEntry> getDetailValues() {
+        return privateArray;
+    }
+
+    public ArrayList<String> getValuesOnly() {
+        ArrayList<String> valueList = new ArrayList<String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            valueList.add(p.mValue);
+        }
+
+        return valueList;
+    }
+
+    public HashMap<String, String> getDetailsHashMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mValue == null) {
+                map.put(p.mKey, "");
+            } else {
+                map.put(p.mKey, p.mValue);
+            }
+        }
+
+        return map;
+    }
+
+    public String getDetailString(String key) {
+        String value = "";
+
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                value = p.mValue;
+                return value;
+            }
+        }
+
+        return value;
+    }
+
+    public void setDetailString(String key, String newValue) {
+        for (int i = 0; i < privateArray.size(); ++i) {
+            PreferenceEntry p = privateArray.get(i);
+            if (p.mKey.equals(key)) {
+                privateArray.get(i).mValue = newValue;
+            }
+        }
+
+    }
+
+    public boolean getDetailBoolean(String key) {
+        for (AccountDetail.PreferenceEntry p : privateArray) {
+            if (p.mKey.equals(key)) {
+                return p.mValue.contentEquals("true");
+            }
+        }
+        return false;
+    }
+}
diff --git a/ring-android/src/cx/ring/model/account/CredentialsManager.java b/ring-android/src/cx/ring/model/account/CredentialsManager.java
new file mode 100644
index 0000000..f3ae1a2
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/CredentialsManager.java
@@ -0,0 +1,130 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model.account;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import cx.ring.views.CredentialsPreference;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+
+public class CredentialsManager {
+
+    PreferenceScreen mScreen;
+    public static final String CURRENT_CRED = "current_cred";
+    public static final String NEW_CRED = "new_cred";
+    private Context mContext;
+    private Account mAccount;
+
+    
+    
+    public void onCreate(Context cont, PreferenceScreen preferenceScreen, Account acc) {
+        mContext = cont;
+        mScreen = preferenceScreen;
+        mAccount = acc;
+    }
+
+    public void reloadCredentials() {
+        removeAllCredentials();
+        addAllCredentials();
+    }
+
+    public void setAddCredentialListener() {
+        mScreen.findPreference("Add.credentials").setOnPreferenceChangeListener(addCredentialListener);
+    }
+
+    public void setEditCredentialListener() {
+        mScreen.findPreference("Add.credentials").setOnPreferenceChangeListener(addCredentialListener);
+    }
+
+    private void addAllCredentials() {
+        ArrayList<AccountCredentials> credentials = mAccount.getCredentials();
+        for (AccountCredentials cred : credentials) {
+            CredentialsPreference toAdd = new CredentialsPreference(mContext, null);
+            toAdd.setKey("credential");
+            toAdd.setTitle(cred.getDetailString(AccountCredentials.CONFIG_ACCOUNT_USERNAME));
+            toAdd.setSummary(cred.getDetailString(AccountCredentials.CONFIG_ACCOUNT_REALM));
+            toAdd.getExtras().putSerializable(CURRENT_CRED, cred.getDetailsHashMap());
+            toAdd.setOnPreferenceChangeListener(editCredentialListener);
+            toAdd.setIcon(null);
+            mScreen.addPreference(toAdd);
+        }
+
+    }
+
+    private void removeAllCredentials() {
+        Preference toRemove = mScreen.findPreference("credential");
+        while (mScreen.findPreference("credential") != null) {
+            mScreen.removePreference(toRemove);
+            toRemove = mScreen.findPreference("credential");
+        }
+    }
+    
+    private OnPreferenceChangeListener editCredentialListener = new OnPreferenceChangeListener() {
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            
+            // We need the old and new value to correctly edit the list of credentials
+            Bundle result = (Bundle) newValue;
+            mAccount.removeCredential(new AccountCredentials((HashMap<String, String>) result.get(CURRENT_CRED)));
+            
+            if(result.get(NEW_CRED) != null){
+                // There is a new value for this credentials it means it has been edited (otherwise deleted)
+                mAccount.addCredential(new AccountCredentials((HashMap<String, String>) result.get(NEW_CRED)));
+            }
+            mAccount.notifyObservers();
+            reloadCredentials();
+            return false;
+        }
+    };
+    
+    private OnPreferenceChangeListener addCredentialListener = new OnPreferenceChangeListener() {
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            mAccount.addCredential((AccountCredentials) newValue);
+            mAccount.notifyObservers();
+            reloadCredentials();
+            return false;
+        }
+    };
+
+
+
+}
diff --git a/ring-android/src/cx/ring/model/account/SRTPManager.java b/ring-android/src/cx/ring/model/account/SRTPManager.java
new file mode 100644
index 0000000..1414d0d
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/SRTPManager.java
@@ -0,0 +1,88 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model.account;
+
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+
+public class SRTPManager {
+    PreferenceScreen mScreen;
+    private Account mAccount;
+
+    public void onCreate(PreferenceScreen preferenceScreen, Account acc) {
+        mScreen = preferenceScreen;
+        mAccount = acc;
+        
+        setDetails();
+    }
+
+    private void setDetails() {
+        for (int i = 0; i < mScreen.getPreferenceCount(); ++i) {
+            ((CheckBoxPreference) mScreen.getPreference(i)).setChecked(mAccount.getSrtpDetails().getDetailBoolean(mScreen.getPreference(i).getKey()));
+        }
+    }
+
+    public void setSDESListener() {
+        mScreen.findPreference("SRTP.rtpFallback").setOnPreferenceChangeListener(toggleFallbackListener);
+    }
+
+    private OnPreferenceChangeListener toggleFallbackListener = new OnPreferenceChangeListener() {
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            mAccount.getSrtpDetails().setDetailString(AccountDetailSrtp.CONFIG_SRTP_RTP_FALLBACK, Boolean.toString((Boolean) newValue));
+            mAccount.notifyObservers();
+            return true;
+        }
+    };
+
+    public void setZRTPListener() {
+        for (int i = 0; i < mScreen.getPreferenceCount(); ++i) {
+            mScreen.getPreference(i).setOnPreferenceChangeListener(zrtpListener);
+        }
+    }
+
+    private OnPreferenceChangeListener zrtpListener = new OnPreferenceChangeListener() {
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            Log.i("SRTP", "Setting " + preference.getKey() + " to" + (Boolean) newValue);
+            mAccount.getSrtpDetails().setDetailString(preference.getKey(), Boolean.toString((Boolean) newValue));
+            mAccount.notifyObservers();
+            return true;
+        }
+    };
+
+}
diff --git a/ring-android/src/cx/ring/model/account/TLSManager.java b/ring-android/src/cx/ring/model/account/TLSManager.java
new file mode 100644
index 0000000..c2f6dca
--- /dev/null
+++ b/ring-android/src/cx/ring/model/account/TLSManager.java
@@ -0,0 +1,221 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.model.account;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import cx.ring.R;
+import cx.ring.fragments.NestedSettingsFragment;
+
+import java.io.File;
+
+public class TLSManager {
+    private static final String TAG = TLSManager.class.getSimpleName();
+    private static final int SELECT_CA_LIST_RC = 42;
+    private static final int SELECT_PRIVATE_KEY_RC = 43;
+    private static final int SELECT_CERTIFICATE_RC = 44;
+    private OnPreferenceClickListener filePickerListener = new OnPreferenceClickListener() {
+        @Override
+        public boolean onPreferenceClick(Preference preference) {
+            if (preference.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE)) {
+                performFileSearch(SELECT_CA_LIST_RC);
+            }
+            if (preference.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE)) {
+                performFileSearch(SELECT_PRIVATE_KEY_RC);
+            }
+            if (preference.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE)) {
+                performFileSearch(SELECT_CERTIFICATE_RC);
+            }
+            return true;
+        }
+    };
+    PreferenceScreen mScreen;
+    private Account mAccount;
+    private NestedSettingsFragment mFrag;
+    private OnPreferenceChangeListener tlsListener = new OnPreferenceChangeListener() {
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            Log.i("TLS", "Setting " + preference.getKey() + " to" + newValue);
+
+            if (preference.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_ENABLE)) {
+                togglePreferenceScreen((Boolean) newValue);
+                if(((Boolean)newValue)){
+                    mAccount.getAdvancedDetails().setDetailString(AccountDetailAdvanced.CONFIG_STUN_ENABLE, Boolean.toString(false));
+                }
+            }
+
+            if (preference instanceof CheckBoxPreference) {
+                mAccount.getTlsDetails().setDetailString(preference.getKey(), Boolean.toString((Boolean) newValue));
+            } else {
+                preference.setSummary((String) newValue);
+                mAccount.getTlsDetails().setDetailString(preference.getKey(), (String) newValue);
+            }
+            mAccount.notifyObservers();
+            return true;
+        }
+    };
+
+    public void onCreate(NestedSettingsFragment con, PreferenceScreen preferenceScreen, Account acc) {
+        mFrag = con;
+        mScreen = preferenceScreen;
+        mAccount = acc;
+        setDetails();
+    }
+
+    private void setDetails() {
+        boolean activated = mAccount.getTlsDetails().getDetailBoolean(AccountDetailTls.CONFIG_TLS_ENABLE);
+
+        for (int i = 0; i < mScreen.getPreferenceCount(); ++i) {
+
+            Preference current = mScreen.getPreference(i);
+
+            if (current instanceof CheckBoxPreference) {
+                ((CheckBoxPreference) mScreen.getPreference(i)).setChecked(mAccount.getTlsDetails().getDetailBoolean(
+                        mScreen.getPreference(i).getKey()));
+            } else {
+                if (current.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE)) {
+                    File crt = new File(mAccount.getTlsDetails().getDetailString(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE));
+                    current.setSummary(crt.getName());
+                    current.setOnPreferenceClickListener(filePickerListener);
+                    setFeedbackIcon(current, crt.getAbsolutePath());
+                } else if (current.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE)) {
+                    current.setSummary(new File(mAccount.getTlsDetails().getDetailString(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE)).getName());
+                    current.setOnPreferenceClickListener(filePickerListener);
+                } else if (current.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE)) {
+                    File pem = new File(mAccount.getTlsDetails().getDetailString(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE));
+                    current.setSummary(pem.getName());
+                    current.setOnPreferenceClickListener(filePickerListener);
+                    setFeedbackIcon(current, pem.getAbsolutePath());
+                    checkForRSAKey(pem.getAbsolutePath());
+                } else if (current.getKey().contentEquals(AccountDetailTls.CONFIG_TLS_METHOD)) {
+                    String[] values = mFrag.getTlsMethods();
+                    ((ListPreference)current).setEntries(values);
+                    ((ListPreference)current).setEntryValues(values);
+                    current.setSummary(mAccount.getTlsDetails().getDetailString(mScreen.getPreference(i).getKey()));
+                } else {
+                    current.setSummary(mAccount.getTlsDetails().getDetailString(mScreen.getPreference(i).getKey()));
+                }
+            }
+
+            // First Preference should remain enabled, it's the actual switch TLS.enable
+            if (i > 0)
+                current.setEnabled(activated);
+
+        }
+    }
+
+    private void checkForRSAKey(String path) {
+        if(mFrag.findRSAKey(path)){
+            mScreen.findPreference(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE).setEnabled(false);
+        }else {
+            mScreen.findPreference(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE).setEnabled(true);
+        }
+    }
+
+    private void setFeedbackIcon(Preference current, String crtPath) {
+        if(!mFrag.checkCertificate(crtPath)){
+            current.setIcon(R.drawable.ic_error);
+        } else {
+            current.setIcon(R.drawable.ic_good);
+        }
+    }
+
+    public void setTLSListener() {
+        for (int i = 0; i < mScreen.getPreferenceCount(); ++i) {
+            mScreen.getPreference(i).setOnPreferenceChangeListener(tlsListener);
+        }
+    }
+
+    private void togglePreferenceScreen(Boolean state) {
+        for (int i = 1; i < mScreen.getPreferenceCount(); ++i) {
+            mScreen.getPreference(i).setEnabled(state);
+        }
+    }
+
+    public void performFileSearch(int requestCodeToSet) {
+
+        // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+        // browser.
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+
+        // Filter to only show results that can be "opened", such as a
+        // file (as opposed to a list of contacts or timezones)
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+        // Filter to show only images, using the image MIME data type.
+        // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+        // To search for all documents available via installed storage providers,
+        // it would be "*/*".
+        intent.setType("*/*");
+        mFrag.startActivityForResult(intent, requestCodeToSet);
+    }
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // TODO Extract returned filed for intent and populate correct preference
+
+        if (resultCode == Activity.RESULT_CANCELED)
+            return;
+
+        File myFile = new File(data.getData().getEncodedPath());
+        Preference pref;
+        switch (requestCode) {
+            case SELECT_CA_LIST_RC:
+                pref = mScreen.findPreference(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE);
+                pref.setSummary(myFile.getName());
+                mAccount.getTlsDetails().setDetailString(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE, myFile.getAbsolutePath());
+                mAccount.notifyObservers();
+                setFeedbackIcon(pref, myFile.getAbsolutePath());
+                break;
+            case SELECT_PRIVATE_KEY_RC:
+                mScreen.findPreference(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE).setSummary(myFile.getName());
+                mAccount.getTlsDetails().setDetailString(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE, myFile.getAbsolutePath());
+                mAccount.notifyObservers();
+                break;
+            case SELECT_CERTIFICATE_RC:
+                pref = mScreen.findPreference(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE);
+                pref.setSummary(myFile.getName());
+                mAccount.getTlsDetails().setDetailString(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE, myFile.getAbsolutePath());
+                mAccount.notifyObservers();
+                setFeedbackIcon(pref, myFile.getAbsolutePath());
+                checkForRSAKey(myFile.getAbsolutePath());
+                break;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/service/CallManagerCallBack.java b/ring-android/src/cx/ring/service/CallManagerCallBack.java
new file mode 100644
index 0000000..622f58f
--- /dev/null
+++ b/ring-android/src/cx/ring/service/CallManagerCallBack.java
@@ -0,0 +1,350 @@
+package cx.ring.service;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import cx.ring.client.CallActivity;
+import cx.ring.model.account.Account;
+import cx.ring.utils.SwigNativeConverter;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import cx.ring.model.CallContact;
+import cx.ring.model.Conference;
+import cx.ring.model.SecureSipCall;
+import cx.ring.model.SipCall;
+import cx.ring.model.SipMessage;
+
+public class CallManagerCallBack extends Callback {
+
+    private static final String TAG = "CallManagerCallBack";
+    private SipService mService;
+
+    static public final String CALL_STATE_CHANGED = "call-state-changed";
+    static public final String INCOMING_CALL = "incoming-call";
+    static public final String INCOMING_TEXT = "incoming-text";
+    static public final String CONF_CREATED = "conf_created";
+    static public final String CONF_REMOVED = "conf_removed";
+    static public final String CONF_CHANGED = "conf_changed";
+    static public final String RECORD_STATE_CHANGED = "record_state";
+
+    static public final String ZRTP_ON = "secure_zrtp_on";
+    static public final String ZRTP_OFF = "secure_zrtp_off";
+    static public final String DISPLAY_SAS = "display_sas";
+    static public final String ZRTP_NEGOTIATION_FAILED = "zrtp_nego_failed";
+    static public final String ZRTP_NOT_SUPPORTED = "zrtp_not_supported";
+
+    static public final String RTCP_REPORT_RECEIVED = "on_rtcp_report_received";
+
+
+    public CallManagerCallBack(SipService context) {
+        super();
+        mService = context;
+    }
+
+    @Override
+    public void callOnStateChange(String callID, String newState) {
+        Log.d(TAG, "on_call_state_changed : (" + callID + ", " + newState + ")");
+
+        Conference toUpdate = mService.findConference(callID);
+
+        if (toUpdate == null) {
+            return;
+        }
+
+        Intent intent = new Intent(CALL_STATE_CHANGED);
+        intent.putExtra("CallID", callID);
+        intent.putExtra("State", newState);
+
+        if (newState.equals("RINGING")) {
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_RINGING);
+        } else if (newState.equals("CURRENT")) {
+            if (toUpdate.isRinging()) {
+                toUpdate.getCallById(callID).setTimestampStart_(System.currentTimeMillis());
+            }
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_CURRENT);
+        } else if (newState.equals("HUNGUP")) {
+            Log.d(TAG, "Hanging up " + callID);
+            SipCall call = toUpdate.getCallById(callID);
+            if (!toUpdate.hasMultipleParticipants()) {
+                if (toUpdate.isRinging() && toUpdate.isIncoming()) {
+                    mService.mNotificationManager.publishMissedCallNotification(mService.getConferences().get(callID));
+                }
+                toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HUNGUP);
+                mService.mHistoryManager.insertNewEntry(toUpdate);
+                mService.getConferences().remove(toUpdate.getId());
+            } else {
+                toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HUNGUP);
+                mService.mHistoryManager.insertNewEntry(call);
+            }
+        } else if (newState.equals("BUSY")) {
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_BUSY);
+            mService.getConferences().remove(toUpdate.getId());
+        } else if (newState.equals("FAILURE")) {
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_FAILURE);
+            mService.getConferences().remove(toUpdate.getId());
+            Ringservice.sflph_call_hang_up(callID);
+        } else if (newState.equals("HOLD")) {
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HOLD);
+        } else if (newState.equals("UNHOLD")) {
+            toUpdate.setCallState(callID, SipCall.state.CALL_STATE_CURRENT);
+        }
+        intent.putExtra("conference", toUpdate);
+        mService.sendBroadcast(intent);
+    }
+
+
+    @Override
+    public void callOnIncomingCall(String accountID, String callID, String from) {
+        Log.d(TAG, "on_incoming_call(" + accountID + ", " + callID + ", " + from + ")");
+
+        try {
+            StringMap details = Ringservice.sflph_config_get_account_details(accountID);
+            VectMap credentials = Ringservice.sflph_config_get_credentials(accountID);
+            Account acc = new Account(accountID, SwigNativeConverter.convertAccountToNative(details), SwigNativeConverter.convertCredentialsToNative(credentials));
+
+            Bundle args = new Bundle();
+            args.putString(SipCall.ID, callID);
+            args.putParcelable(SipCall.ACCOUNT, acc);
+            args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_RINGING);
+            args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_INCOMING);
+
+            CallContact unknow = CallContact.ContactBuilder.buildUnknownContact(from);
+            args.putParcelable(SipCall.CONTACT, unknow);
+
+            Intent toSend = new Intent(CallManagerCallBack.INCOMING_CALL);
+            toSend.setClass(mService, CallActivity.class);
+            toSend.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            SipCall newCall = new SipCall(args);
+            newCall.setTimestampStart_(System.currentTimeMillis());
+
+            Conference toAdd;
+            if (acc.useSecureLayer()) {
+               SecureSipCall secureCall = new SecureSipCall(newCall);
+                toAdd = new Conference(secureCall);
+            } else {
+                toAdd = new Conference(newCall);
+            }
+
+            mService.getConferences().put(toAdd.getId(), toAdd);
+
+            Bundle bundle = new Bundle();
+            bundle.putParcelable("conference", toAdd);
+            toSend.putExtra("resuming", false);
+            toSend.putExtras(bundle);
+            mService.startActivity(toSend);
+            mService.mMediaManager.startRing("");
+            mService.mMediaManager.obtainAudioFocus(true);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void callOnConferenceCreated(final String confID) {
+        Log.w(TAG, "CONFERENCE CREATED:" + confID);
+        Intent intent = new Intent(CONF_CREATED);
+        Conference created = new Conference(confID);
+
+        StringVect all_participants = Ringservice.sflph_call_get_participant_list(confID);
+        Log.w(TAG, "all_participants:" + all_participants.size());
+        for (int i = 0; i < all_participants.size(); ++i) {
+            if (mService.getConferences().get(all_participants.get(i)) != null) {
+                created.addParticipant(mService.getConferences().get(all_participants.get(i)).getCallById(all_participants.get(i)));
+                mService.getConferences().remove(all_participants.get(i));
+            } else {
+                for (Map.Entry<String, Conference> stringConferenceEntry : mService.getConferences().entrySet()) {
+                    Conference tmp = stringConferenceEntry.getValue();
+                    for (SipCall c : tmp.getParticipants()) {
+                        if (c.getCallId().contentEquals(all_participants.get(i))) {
+                            created.addParticipant(c);
+                            mService.getConferences().get(tmp.getId()).removeParticipant(c);
+                        }
+                    }
+                }
+            }
+        }
+        intent.putExtra("conference", created);
+        intent.putExtra("confID", created.getId());
+        mService.getConferences().put(created.getId(), created);
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnIncomingMessage(String ID, String from, String msg) {
+        Log.w(TAG, "on_incoming_message:" + msg);
+        Intent intent = new Intent(INCOMING_TEXT);
+        intent.putExtra("CallID", ID);
+        intent.putExtra("From", from);
+        intent.putExtra("Msg", msg);
+
+        if (mService.getConferences().get(ID) != null) {
+            mService.getConferences().get(ID).addSipMessage(new SipMessage(true, msg));
+            intent.putExtra("conference", mService.getConferences().get(ID));
+        } else {
+            for (Map.Entry<String, Conference> stringConferenceEntry : mService.getConferences().entrySet()) {
+                Conference tmp = stringConferenceEntry.getValue();
+                for (SipCall c : tmp.getParticipants()) {
+                    if (c.getCallId().contentEquals(ID)) {
+                        mService.getConferences().get(tmp.getId()).addSipMessage(new SipMessage(true, msg));
+                        intent.putExtra("conference", tmp);
+                    }
+                }
+            }
+
+        }
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnConferenceRemove(String confID) {
+        Log.i(TAG, "on_conference_removed:");
+        Intent intent = new Intent(CONF_REMOVED);
+        intent.putExtra("confID", confID);
+
+        Conference toReInsert = mService.getConferences().get(confID);
+        for (SipCall call : toReInsert.getParticipants()) {
+            mService.getConferences().put(call.getCallId(), new Conference(call));
+        }
+        intent.putExtra("conference", mService.getConferences().get(confID));
+        mService.getConferences().remove(confID);
+        mService.sendBroadcast(intent);
+
+    }
+
+    @Override
+    public void callOnConferenceChanged(String confID, String state) {
+        Log.i(TAG, "on_conference_state_changed:");
+        Intent intent = new Intent(CONF_CHANGED);
+        intent.putExtra("confID", confID);
+        intent.putExtra("State", state);
+
+
+        Log.i(TAG, "Received:" + intent.getAction());
+        Log.i(TAG, "State:" + state);
+
+        Conference toModify = mService.getConferences().get(confID);
+        toModify.setCallState(confID, state);
+
+        ArrayList<String> newParticipants = SwigNativeConverter.convertSwigToNative(Ringservice.sflph_call_get_participant_list(intent.getStringExtra("confID")));
+
+        if (toModify.getParticipants().size() < newParticipants.size()) {
+            // We need to add the new participant to the conf
+            for (String newParticipant : newParticipants) {
+                if (toModify.getCallById(newParticipant) == null) {
+                    mService.addCallToConference(toModify.getId(), newParticipant);
+                }
+            }
+        } else if (toModify.getParticipants().size() > newParticipants.size()) {
+            Log.i(TAG, "toModify.getParticipants().size() > newParticipants.size()");
+            for (SipCall participant : toModify.getParticipants()) {
+                if (!newParticipants.contains(participant.getCallId())) {
+                    mService.detachCallFromConference(toModify.getId(), participant);
+                    break;
+                }
+            }
+        }
+
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnRecordPlaybackFilepath(String id, String filename) {
+        Intent intent = new Intent();
+        intent.putExtra("callID", id);
+        intent.putExtra("file", filename);
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnSecureSdesOn(String callID) {
+        Log.i(TAG, "on_secure_sdes_on");
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setInitialized();
+        call.useSecureSDES(true);
+    }
+
+    @Override
+    public void callOnSecureSdesOff(String callID) {
+        Log.i(TAG, "on_secure_sdes_off");
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setInitialized();
+        call.useSecureSDES(false);
+    }
+
+    @Override
+    public void callOnSecureZrtpOn(String callID, String cipher) {
+        Log.i(TAG, "on_secure_zrtp_on");
+        Intent intent = new Intent(ZRTP_ON);
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setInitialized();
+        call.setZrtpSupport(true);
+        intent.putExtra("callID", callID);
+        intent.putExtra("conference", mService.findConference(callID));
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnSecureZrtpOff(String callID) {
+        Log.i(TAG, "on_secure_zrtp_off");
+        Intent intent = new Intent(ZRTP_OFF);
+        intent.putExtra("callID", callID);
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        // Security can be off because call was hung up
+        if (call == null)
+            return;
+
+        call.setInitialized();
+        call.setZrtpSupport(false);
+        intent.putExtra("conference", mService.findConference(callID));
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnShowSas(String callID, String sas, int verified) {
+        Log.i(TAG, "on_show_sas:" + sas);
+        Intent intent = new Intent(DISPLAY_SAS);
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setSAS(sas);
+        call.sasConfirmedByZrtpLayer(verified);
+
+        intent.putExtra("callID", callID);
+        intent.putExtra("SAS", sas);
+        intent.putExtra("verified", verified);
+        intent.putExtra("conference", mService.findConference(callID));
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnZrtpNotSuppOther(String callID) {
+        Log.i(TAG, "on_zrtp_not_supported");
+        Intent intent = new Intent(ZRTP_NOT_SUPPORTED);
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setInitialized();
+        call.setZrtpSupport(false);
+        intent.putExtra("callID", callID);
+        intent.putExtra("conference", mService.findConference(callID));
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnZrtpNegotiationFail(String callID, String reason, String severity) {
+        Log.i(TAG, "on_zrtp_negociation_failed");
+        Intent intent = new Intent(ZRTP_NEGOTIATION_FAILED);
+        SecureSipCall call = (SecureSipCall) mService.getCallById(callID);
+        call.setInitialized();
+        call.setZrtpSupport(false);
+        intent.putExtra("callID", callID);
+        intent.putExtra("conference", mService.findConference(callID));
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void callOnRtcpReceiveReport(String callID, IntegerMap stats) {
+        Log.i(TAG, "on_rtcp_report_received");
+        Intent intent = new Intent(RTCP_REPORT_RECEIVED);
+        mService.sendBroadcast(intent);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/service/ConfigurationManagerCallback.java b/ring-android/src/cx/ring/service/ConfigurationManagerCallback.java
new file mode 100644
index 0000000..953a655
--- /dev/null
+++ b/ring-android/src/cx/ring/service/ConfigurationManagerCallback.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.service;
+
+import android.content.Intent;
+import android.util.Log;
+
+public class ConfigurationManagerCallback extends ConfigurationCallback {
+
+    private  SipService mService;
+    private static final String TAG = "ConfigurationManagerCallback";
+
+    static public final String ACCOUNTS_CHANGED = "accounts-changed";
+    static public final String ACCOUNT_STATE_CHANGED = "account-state-changed";
+
+    public ConfigurationManagerCallback(SipService context) {
+        super();
+        mService = context;
+    }
+
+    @Override
+    public void configOnVolumeChange(String device, int value) {
+        super.configOnVolumeChange(device, value);
+    }
+
+    @Override
+    public void configOnAccountsChange() {
+        super.configOnAccountsChange();
+        Intent intent = new Intent(ACCOUNTS_CHANGED);
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void configOnStunStatusFail(String account_id) {
+        Log.d(TAG, "configOnStunStatusFail : (" + account_id);
+    }
+
+    @Override
+    public void configOnRegistrationStateChange(String accoundID, int state) {
+        String strState = "";
+        switch (state){
+        case 0:
+            strState = "UNREGISTERED";
+            break;
+        case 1:
+            strState = "TRYING";
+            break;
+        case 2:
+            strState = "REGISTERED";
+            break;
+        case 3:
+            strState = "ERROR_GENERIC";
+            break;
+        case 4:
+            strState = "ERROR_AUTH";
+            break;
+        case 5:
+            strState = "ERROR_NETWORK";
+            break;
+        case 6:
+            strState = "ERROR_HOST";
+            break;
+        case 7:
+            strState = "ERROR_EXIST_STUN";
+            break;
+        case 8:
+            strState = "ERROR_NOT_ACCEPTABLE";
+            break;
+        case 9:
+            strState = "NUMBER_OF_STATES";
+            break;
+        }
+
+        sendAccountStateChangedMessage(accoundID, strState, 0);
+    }
+
+    @Override
+    public void configOnSipRegistrationStateChange(String account_id, String state, int code) {
+
+    }
+
+    @Override
+    public void configOnError(int alert) {
+        Log.d(TAG, "configOnError : (" + alert);
+    }
+
+    private void sendAccountStateChangedMessage(String accoundID, String state, int code) {
+        Intent intent = new Intent(ACCOUNT_STATE_CHANGED);
+        intent.putExtra("Account", accoundID);
+        intent.putExtra("state", state);
+        intent.putExtra("code", code);
+        mService.sendBroadcast(intent);
+    }
+
+    public IntVect configGetHardwareAudioFormat(){
+        IntVect result = new IntVect();
+
+        OpenSlParams audioParams = OpenSlParams.createInstance(mService);
+        result.add(audioParams.getSampleRate());
+        result.add(audioParams.getBufferSize());
+
+        return result;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/service/ISipService.aidl b/ring-android/src/cx/ring/service/ISipService.aidl
new file mode 100644
index 0000000..d1ea05a
--- /dev/null
+++ b/ring-android/src/cx/ring/service/ISipService.aidl
@@ -0,0 +1,88 @@
+package cx.ring.service;
+
+import cx.ring.model.SipCall;
+import cx.ring.model.Conference;
+import cx.ring.model.SipMessage;
+
+interface ISipService {
+    
+    Map getCallDetails(in String callID);
+    void placeCall(in SipCall call);
+    void refuse(in String callID);
+    void accept(in String callID);
+    void hangUp(in String callID);
+    void hold(in String callID);
+    void unhold(in String callID);
+    
+    List getAccountList();
+    String addAccount(in Map accountDetails);
+    void removeAccount(in String accoundId);
+    void setAccountOrder(in String order);
+    Map getAccountDetails(in String accountID);
+    Map getAccountTemplate();
+    void registerAllAccounts();
+    void setAccountDetails(in String accountId, in Map accountDetails);
+    List getCredentials(in String accountID);
+    void setCredentials(in String accountID, in List creds);
+    void setAudioPlugin(in String callID);
+    String getCurrentAudioOutputPlugin();
+    List getAudioCodecList(in String accountID);
+    void setActiveCodecList(in List codecs, in String accountID);
+    Map getRingtoneList();
+
+    boolean checkForPrivateKey(in String pemPath);
+    boolean checkCertificateValidity(in String pemPath);
+    boolean checkHostnameCertificate(in String certificatePath, in String host, in String port);
+    
+    
+    // FIXME
+    void toggleSpeakerPhone(in boolean toggle);
+
+    /* Recording */
+    void setRecordPath(in String path);
+    String getRecordPath();
+    boolean toggleRecordingCall(in String id);
+    boolean startRecordedFilePlayback(in String filepath);
+	void stopRecordedFilePlayback(in String filepath);
+	
+	/* Mute */
+	void setMuted(boolean mute);
+    boolean isCaptureMuted();
+
+    /* Security */
+    void confirmSAS(in String callID);
+    List getTlsSupportedMethods();
+
+	/* DTMF */
+	void playDtmf(in String key);
+    
+    /* IM */
+    void sendTextMessage(in String callID, in SipMessage message);
+        
+    void transfer(in String callID, in String to);
+    void attendedTransfer(in String transferID, in String targetID);
+    
+    /* Conference related methods */
+
+    void removeConference(in String confID);
+    void joinParticipant(in String sel_callID, in String drag_callID);
+
+    void addParticipant(in SipCall call, in String confID);
+    void addMainParticipant(in String confID);
+    void detachParticipant(in String callID);
+    void joinConference(in String sel_confID, in String drag_confID);
+    void hangUpConference(in String confID);
+    void holdConference(in String confID);
+    void unholdConference(in String confID);
+    boolean isConferenceParticipant(in String callID);
+    Map getConferenceList();
+    List getParticipantList(in String confID);
+    String getConferenceId(in String callID);
+    String getConferenceDetails(in String callID);
+    
+    Conference getCurrentCall();
+    List getConcurrentCalls();
+
+    Conference getConference(in String id);
+
+}
diff --git a/ring-android/src/cx/ring/service/OpenSlParams.java b/ring-android/src/cx/ring/service/OpenSlParams.java
new file mode 100644
index 0000000..65c73d6
--- /dev/null
+++ b/ring-android/src/cx/ring/service/OpenSlParams.java
@@ -0,0 +1,86 @@
+package cx.ring.service;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * This class illustrates how to query OpenSL config parameters on Jelly Bean MR1 while maintaining
+ * backward compatibility with older versions of Android. The trick is to place the new API calls in
+ * an inner class that will only be loaded if we're running on JB MR1 or later.
+ */
+public abstract class OpenSlParams {
+
+  /**
+   * @return The recommended sample rate in Hz.
+   */
+  public abstract int getSampleRate();
+
+  /**
+   * @return The recommended buffer size in frames.
+   */
+  public abstract int getBufferSize();
+
+  /**
+   * @param context, e.g., the current activity.
+   * @return OpenSlParams instance for the given context.
+   */
+  public static OpenSlParams createInstance(Context context) {
+    return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
+        ? new JellyBeanMr1OpenSlParams(context)
+        : new DefaultOpenSlParams();
+  }
+
+  private OpenSlParams() {
+    // Not meant to be instantiated except here.
+  }
+
+  // Implementation for Jelly Bean MR1 or later.
+  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+  private static class JellyBeanMr1OpenSlParams extends OpenSlParams {
+
+    private final int sampleRate;
+    private final int bufferSize;
+
+    private JellyBeanMr1OpenSlParams(Context context) {
+      AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+      // Provide default values in case config lookup fails.
+      int sr = 44100;
+      int bs = 64;
+      try {
+        // If possible, query the native sample rate and buffer size.
+        sr = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+        bs = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
+      } catch (NumberFormatException e) {
+        Log.w(getClass().getName(), "Failed to read native OpenSL config: " + e);
+      }
+      sampleRate = sr;
+      bufferSize = bs;
+    }
+
+    @Override
+    public int getSampleRate() {
+      return sampleRate;
+    }
+
+    @Override
+    public int getBufferSize() {
+      return bufferSize;
+    }
+  }
+
+  // Default factory for Jelly Bean or older.
+  private static class DefaultOpenSlParams extends OpenSlParams {
+    @Override
+    public int getSampleRate() {
+      return 44100;
+    }
+
+    @Override
+    public int getBufferSize() {
+      return 64;
+    }
+  };
+}
diff --git a/ring-android/src/cx/ring/service/ServiceConstants.java b/ring-android/src/cx/ring/service/ServiceConstants.java
new file mode 100644
index 0000000..ab175ab
--- /dev/null
+++ b/ring-android/src/cx/ring/service/ServiceConstants.java
@@ -0,0 +1,15 @@
+package cx.ring.service;
+
+public final class ServiceConstants {
+
+
+    public interface call {
+        public static final String CALL_TYPE = "CALL_TYPE";
+        public static final String PEER_NUMBER = "PEER_NUMBER";
+        public static final String DISPLAY_NAME = "DISPLAY_NAME";
+        public static final String CALL_STATE = "CALL_STATE";
+        public static final String CONF_ID = "CONF_ID";
+        public static final String TIMESTAMP_START = "TIMESTAMP_START";
+        public static final String ACCOUNTID = "ACCOUNTID";
+    }
+}
diff --git a/ring-android/src/cx/ring/service/SipService.java b/ring-android/src/cx/ring/service/SipService.java
new file mode 100644
index 0000000..14d907a
--- /dev/null
+++ b/ring-android/src/cx/ring/service/SipService.java
@@ -0,0 +1,1292 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Regis Montoya <r3gis.3R@gmail.com>
+ *  Author: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
+ *          Alexandre Lision <alexandre.lision@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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.service;
+
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.*;
+import android.util.Log;
+import cx.ring.history.HistoryManager;
+import cx.ring.model.Codec;
+import cx.ring.model.Conference;
+import cx.ring.model.SecureSipCall;
+import cx.ring.model.SipMessage;
+import cx.ring.utils.MediaManager;
+import cx.ring.utils.SipNotifications;
+import cx.ring.utils.SwigNativeConverter;
+import cx.ring.model.SipCall;
+
+
+public class SipService extends Service {
+
+    static final String TAG = "SipService";
+    private SipServiceExecutor mExecutor;
+    private static HandlerThread executorThread;
+
+    private Handler handler = new Handler();
+    private static int POLLING_TIMEOUT = 500;
+    private Runnable pollEvents = new Runnable() {
+        @Override
+        public void run() {
+            Ringservice.sflph_poll_events();
+            handler.postDelayed(this, POLLING_TIMEOUT);
+        }
+    };
+    private boolean isPjSipStackStarted = false;
+
+    protected SipNotifications mNotificationManager;
+    protected HistoryManager mHistoryManager;
+    protected MediaManager mMediaManager;
+
+    private HashMap<String, Conference> mConferences = new HashMap<String, Conference>();
+    private ConfigurationManagerCallback configurationCallback;
+    private CallManagerCallBack callManagerCallBack;
+
+    public HashMap<String, Conference> getConferences() {
+        return mConferences;
+    }
+
+    public void addCallToConference(String confId, String callId) {
+        if(mConferences.get(callId) != null){
+            // We add a simple call to a conference
+            Log.i(TAG, "// We add a simple call to a conference");
+            mConferences.get(confId).addParticipant(mConferences.get(callId).getParticipants().get(0));
+            mConferences.remove(callId);
+        } else {
+            Log.i(TAG, "addCallToConference");
+            for (Entry<String, Conference> stringConferenceEntry : mConferences.entrySet()) {
+                Conference tmp = stringConferenceEntry.getValue();
+                for (SipCall c : tmp.getParticipants()) {
+                    if (c.getCallId().contentEquals(callId)) {
+                        mConferences.get(confId).addParticipant(c);
+                        mConferences.get(tmp.getId()).removeParticipant(c);
+                    }
+                }
+            }
+        }
+    }
+
+    public void detachCallFromConference(String confId, SipCall call) {
+        Log.i(TAG, "detachCallFromConference");
+        Conference separate = new Conference(call);
+        mConferences.put(separate.getId(), separate);
+        mConferences.get(confId).removeParticipant(call);
+    }
+
+    @Override
+    public boolean onUnbind(Intent i) {
+        super.onUnbind(i);
+        Log.i(TAG, "onUnbind(intent)");
+        return true;
+    }
+
+    @Override
+    public void onRebind(Intent i) {
+        super.onRebind(i);
+    }
+
+    /* called once by startService() */
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreated");
+        super.onCreate();
+
+        getExecutor().execute(new StartRunnable());
+
+        mNotificationManager = new SipNotifications(this);
+        mMediaManager = new MediaManager(this);
+        mHistoryManager = new HistoryManager(this);
+
+        mNotificationManager.onServiceCreate();
+        mMediaManager.startService();
+
+    }
+
+    /* called for each startService() */
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "onStarted");
+        super.onStartCommand(intent, flags, startId);
+        return START_STICKY; /* started and stopped explicitly */
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "onDestroy");
+        /* called once by stopService() */
+        mNotificationManager.onServiceDestroy();
+        mMediaManager.stopService();
+        getExecutor().execute(new FinalizeRunnable());
+        super.onDestroy();
+
+    }
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        Log.i(TAG, "onBound");
+        return mBinder;
+    }
+
+    private static Looper createLooper() {
+        if (executorThread == null) {
+            Log.d(TAG, "Creating new handler thread");
+            // ADT gives a fake warning due to bad parse rule.
+            executorThread = new HandlerThread("SipService.Executor");
+            executorThread.start();
+        }
+        return executorThread.getLooper();
+    }
+
+    public SipServiceExecutor getExecutor() {
+        // create mExecutor lazily
+        if (mExecutor == null) {
+            mExecutor = new SipServiceExecutor();
+        }
+        return mExecutor;
+    }
+
+    public SipCall getCallById(String callID) {
+        if (getConferences().get(callID) != null) {
+            return getConferences().get(callID).getCallById(callID);
+        } else {
+            // Check if call is in a conference
+            for (Entry<String, Conference> stringConferenceEntry : getConferences().entrySet()) {
+                Conference tmp = stringConferenceEntry.getValue();
+                SipCall c = tmp.getCallById(callID);
+                if (c != null)
+                    return c;
+            }
+        }
+        return null;
+    }
+
+    // Executes immediate tasks in a single executorThread.
+    public static class SipServiceExecutor extends Handler {
+
+        SipServiceExecutor() {
+            super(createLooper());
+        }
+
+        public void execute(Runnable task) {
+            // TODO: add wakelock
+            Message.obtain(SipServiceExecutor.this, 0/* don't care */, task).sendToTarget();
+            Log.w(TAG, "SenT!");
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.w(TAG, "handleMessage");
+            if (msg.obj instanceof Runnable) {
+                executeInternal((Runnable) msg.obj);
+            } else {
+                Log.w(TAG, "can't handle msg: " + msg);
+            }
+        }
+
+        private void executeInternal(Runnable task) {
+            try {
+                task.run();
+            } catch (Throwable t) {
+                Log.e(TAG, "run task: " + task, t);
+            }
+        }
+    }
+
+    private void stopDaemon() {
+        handler.removeCallbacks(pollEvents);
+        Ringservice.sflph_fini();
+        isPjSipStackStarted = false;
+    }
+
+    private void startPjSipStack() throws SameThreadException {
+        if (isPjSipStackStarted)
+            return;
+
+        try {
+            System.loadLibrary("codec_ulaw");
+            System.loadLibrary("codec_alaw");
+            System.loadLibrary("codec_speex");
+            System.loadLibrary("codec_g729");
+            System.loadLibrary("codec_gsm");
+            System.loadLibrary("codec_opus");
+            System.loadLibrary("sflphonejni");
+            isPjSipStackStarted = true;
+
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "Problem with the current Pj stack...", e);
+            isPjSipStackStarted = false;
+            return;
+        } catch (Exception e) {
+            Log.e(TAG, "Problem with the current Pj stack...", e);
+            isPjSipStackStarted = false;
+        }
+
+        configurationCallback = new ConfigurationManagerCallback(this);
+        callManagerCallBack = new CallManagerCallBack(this);
+        Ringservice.init(configurationCallback, callManagerCallBack);
+        handler.postDelayed(pollEvents, POLLING_TIMEOUT);
+        Log.i(TAG, "PjSIPStack started");
+    }
+
+    // Enforce same thread contract to ensure we do not call from somewhere else
+    public class SameThreadException extends Exception {
+        private static final long serialVersionUID = -905639124232613768L;
+
+        public SameThreadException() {
+            super("Should be launched from a single worker thread");
+        }
+    }
+
+    public abstract static class SipRunnable implements Runnable {
+        protected abstract void doRun() throws SameThreadException, RemoteException;
+
+        @Override
+        public void run() {
+            try {
+                doRun();
+            } catch (SameThreadException e) {
+                Log.e(TAG, "Not done from same thread");
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    public abstract class SipRunnableWithReturn implements Runnable {
+        Object obj = null;
+        boolean done = false;
+
+        protected abstract Object doRun() throws SameThreadException, RemoteException;
+
+        public Object getVal() {
+            return obj;
+        }
+
+        public boolean isDone() {
+            return done;
+        }
+
+        @Override
+        public void run() {
+            try {
+                if (isPjSipStackStarted)
+                    obj = doRun();
+                done = true;
+            } catch (SameThreadException e) {
+                Log.e(TAG, "Not done from same thread");
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    class StartRunnable extends SipRunnable {
+        @Override
+        protected void doRun() throws SameThreadException {
+            startPjSipStack();
+        }
+    }
+
+    class FinalizeRunnable extends SipRunnable {
+        @Override
+        protected void doRun() throws SameThreadException {
+            stopDaemon();
+        }
+    }
+
+    /* ************************************
+     *
+     * Implement public interface for the service
+     *
+     * *********************************
+     */
+
+    private final ISipService.Stub mBinder = new ISipService.Stub() {
+
+        @Override
+        public void placeCall(final SipCall call) {
+
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.placeCall() thread running...");
+                    Conference toAdd;
+                    if(call.getAccount().useSecureLayer()){
+                        SecureSipCall secureCall = new SecureSipCall(call);
+                        toAdd = new Conference(secureCall);
+                    } else {
+                        toAdd = new Conference(call);
+                    }
+                    mConferences.put(toAdd.getId(), toAdd);
+                    mMediaManager.obtainAudioFocus(false);
+                    Ringservice.sflph_call_place(call.getAccount().getAccountID(), call.getCallId(), call.getmContact().getPhones().get(0).getNumber());
+                }
+            });
+        }
+
+        @Override
+        public void refuse(final String callID) {
+
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.refuse() thread running...");
+                    Ringservice.sflph_call_refuse(callID);
+                }
+            });
+        }
+
+        @Override
+        public void accept(final String callID) {
+            mMediaManager.stopRing();
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.accept() thread running...");
+                    Ringservice.sflph_call_accept(callID);
+                    mMediaManager.RouteToInternalSpeaker();
+                }
+            });
+        }
+
+        @Override
+        public void hangUp(final String callID) {
+            mMediaManager.stopRing();
+            Log.e(TAG, "HANGING UP");
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.hangUp() thread running...");
+                    Ringservice.sflph_call_hang_up(callID);
+                    removeCall(callID);
+                    if(mConferences.size() == 0) {
+                        Log.i(TAG, "No more calls!");
+                        mMediaManager.abandonAudioFocus();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void hold(final String callID) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.hold() thread running...");
+                    Ringservice.sflph_call_hold(callID);
+                }
+            });
+        }
+
+        @Override
+        public void unhold(final String callID) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.unhold() thread running...");
+                    Ringservice.sflph_call_unhold(callID);
+                }
+            });
+        }
+
+        @Override
+        public HashMap<String, String> getCallDetails(String callID) throws RemoteException {
+            class CallDetails extends SipRunnableWithReturn {
+                private String id;
+
+                CallDetails(String callID) {
+                    id = callID;
+                }
+
+                @Override
+                protected StringMap doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getCallDetails() thread running...");
+                    return Ringservice.sflph_call_get_call_details(id);
+                }
+            }
+
+            CallDetails runInstance = new CallDetails(callID);
+            getExecutor().execute(runInstance);
+
+            while (!runInstance.isDone()) {
+            }
+            StringMap swigmap = (StringMap) runInstance.getVal();
+
+            return SwigNativeConverter.convertCallDetailsToNative(swigmap);
+        }
+
+        @Override
+        public void setAudioPlugin(final String audioPlugin) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.setAudioPlugin() thread running...");
+                    Ringservice.sflph_config_set_audio_plugin(audioPlugin);
+                }
+            });
+        }
+
+        @Override
+        public String getCurrentAudioOutputPlugin() {
+            class CurrentAudioPlugin extends SipRunnableWithReturn {
+                @Override
+                protected String doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getCurrentAudioOutputPlugin() thread running...");
+                    return Ringservice.sflph_config_get_current_audio_output_plugin();
+                }
+            }
+
+            CurrentAudioPlugin runInstance = new CurrentAudioPlugin();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+                // Log.e(TAG, "Waiting for Nofing");
+            }
+            return (String) runInstance.getVal();
+        }
+
+        @Override
+        public ArrayList<String> getAccountList() {
+            class AccountList extends SipRunnableWithReturn {
+                @Override
+                protected StringVect doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getAccountList() thread running...");
+                    return Ringservice.sflph_config_get_account_list();
+                }
+            }
+            AccountList runInstance = new AccountList();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            StringVect swigvect = (StringVect) runInstance.getVal();
+
+            ArrayList<String> nativelist = new ArrayList<String>();
+
+            for (int i = 0; i < swigvect.size(); i++)
+                nativelist.add(swigvect.get(i));
+
+            return nativelist;
+        }
+
+        @Override
+        public void setAccountOrder(final String order) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.setAccountsOrder() thread running...");
+                    Ringservice.sflph_config_set_accounts_order(order);
+                }
+            });
+        }
+
+        @Override
+        public HashMap<String, String> getAccountDetails(final String accountID) {
+            class AccountDetails extends SipRunnableWithReturn {
+                private String id;
+
+                AccountDetails(String accountId) {
+                    id = accountId;
+                }
+
+                @Override
+                protected StringMap doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getAccountDetails() thread running...");
+                    return Ringservice.sflph_config_get_account_details(id);
+                }
+            }
+
+            AccountDetails runInstance = new AccountDetails(accountID);
+            getExecutor().execute(runInstance);
+
+            while (!runInstance.isDone()) {
+            }
+            StringMap swigmap = (StringMap) runInstance.getVal();
+
+            return SwigNativeConverter.convertAccountToNative(swigmap);
+        }
+
+        @SuppressWarnings("unchecked")
+        // Hashmap runtime cast
+        @Override
+        public void setAccountDetails(final String accountId, final Map map) {
+            HashMap<String, String> nativemap = (HashMap<String, String>) map;
+
+            final StringMap swigmap = SwigNativeConverter.convertFromNativeToSwig(nativemap);
+
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+
+                    Ringservice.sflph_config_set_account_details(accountId, swigmap);
+                    Log.i(TAG, "SipService.setAccountDetails() thread running...");
+                }
+
+            });
+        }
+
+        @Override
+        public Map getAccountTemplate() throws RemoteException {
+            class AccountTemplate extends SipRunnableWithReturn {
+
+                @Override
+                protected StringMap doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getAccountTemplate() thread running...");
+                    return Ringservice.sflph_config_get_account_template();
+                }
+            }
+
+            AccountTemplate runInstance = new AccountTemplate();
+            getExecutor().execute(runInstance);
+
+            while (!runInstance.isDone()) {
+            }
+            StringMap swigmap = (StringMap) runInstance.getVal();
+
+            return SwigNativeConverter.convertAccountToNative(swigmap);
+        }
+
+        @SuppressWarnings("unchecked")
+        // Hashmap runtime cast
+        @Override
+        public String addAccount(Map map) {
+            class AddAccount extends SipRunnableWithReturn {
+                StringMap map;
+
+                AddAccount(StringMap m) {
+                    map = m;
+                }
+
+                @Override
+                protected String doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.addAccount() thread running...");
+                    return Ringservice.sflph_config_add_account(map);
+                }
+            }
+
+            final StringMap swigmap = SwigNativeConverter.convertFromNativeToSwig((HashMap<String, String>) map);
+
+            AddAccount runInstance = new AddAccount(swigmap);
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            return (String) runInstance.getVal();
+        }
+
+        @Override
+        public void removeAccount(final String accountId) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.setAccountDetails() thread running...");
+                    Ringservice.sflph_config_remove_account(accountId);
+                }
+            });
+        }
+
+        /*************************
+         * Transfer related API
+         *************************/
+
+        @Override
+        public void transfer(final String callID, final String to) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.transfer() thread running...");
+                    if (Ringservice.sflph_call_transfer(callID, to)) {
+                        Bundle bundle = new Bundle();
+                        bundle.putString("CallID", callID);
+                        bundle.putString("State", "HUNGUP");
+                        Intent intent = new Intent(CallManagerCallBack.CALL_STATE_CHANGED);
+                        intent.putExtra("com.savoirfairelinux.sflphone.service.newstate", bundle);
+                        sendBroadcast(intent);
+                    } else
+                        Log.i(TAG, "NOT OK");
+                }
+            });
+
+        }
+
+        @Override
+        public void attendedTransfer(final String transferID, final String targetID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.attendedTransfer() thread running...");
+                    if (Ringservice.sflph_call_attended_transfer(transferID, targetID)) {
+                        Log.i(TAG, "OK");
+                    } else
+                        Log.i(TAG, "NOT OK");
+                }
+            });
+
+        }
+
+        /*************************
+         * Conference related API
+         *************************/
+
+        @Override
+        public void removeConference(final String confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.createConference() thread running...");
+                    Ringservice.sflph_call_remove_conference(confID);
+                }
+            });
+
+        }
+
+        @Override
+        public void joinParticipant(final String sel_callID, final String drag_callID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.joinParticipant() thread running...");
+                    Ringservice.sflph_call_join_participant(sel_callID, drag_callID);
+                    // Generate a CONF_CREATED callback
+                }
+            });
+            Log.i(TAG, "After joining participants");
+        }
+
+        @Override
+        public void addParticipant(final SipCall call, final String confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.addParticipant() thread running...");
+                    Ringservice.sflph_call_add_participant(call.getCallId(), confID);
+                    mConferences.get(confID).getParticipants().add(call);
+                }
+            });
+
+        }
+
+        @Override
+        public void addMainParticipant(final String confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.addMainParticipant() thread running...");
+                    Ringservice.sflph_call_add_main_participant(confID);
+                }
+            });
+
+        }
+
+        @Override
+        public void detachParticipant(final String callID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.detachParticipant() thread running...");
+                    Log.i(TAG, "Detaching " + callID);
+                    Iterator<Entry<String, Conference>> it = mConferences.entrySet().iterator();
+                    Log.i(TAG, "mConferences size " + mConferences.size());
+                    while (it.hasNext()) {
+                        Conference tmp = it.next().getValue();
+                        Log.i(TAG, "conf has " + tmp.getParticipants().size() + " participants");
+                        if (tmp.contains(callID)) {
+                            Conference toDetach = new Conference(tmp.getCallById(callID));
+                            mConferences.put(toDetach.getId(), toDetach);
+                            Log.i(TAG, "Call found and put in current_calls");
+                        }
+                    }
+                    Ringservice.sflph_call_detach_participant(callID);
+                }
+            });
+
+        }
+
+        @Override
+        public void joinConference(final String sel_confID, final String drag_confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.joinConference() thread running...");
+                    Ringservice.sflph_call_join_conference(sel_confID, drag_confID);
+                }
+            });
+
+        }
+
+        @Override
+        public void hangUpConference(final String confID) throws RemoteException {
+            Log.e(TAG, "HANGING UP CONF");
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.joinConference() thread running...");
+                    Ringservice.sflph_call_hang_up_conference(confID);
+                }
+            });
+
+        }
+
+        @Override
+        public void holdConference(final String confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.holdConference() thread running...");
+                    Ringservice.sflph_call_hold_conference(confID);
+                }
+            });
+
+        }
+
+        @Override
+        public void unholdConference(final String confID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.unholdConference() thread running...");
+                    Ringservice.sflph_call_unhold_conference(confID);
+                }
+            });
+
+        }
+
+        @Override
+        public boolean isConferenceParticipant(final String callID) throws RemoteException {
+            class IsParticipant extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.isRecording() thread running...");
+                    return Ringservice.sflph_call_is_conference_participant(callID);
+                }
+            }
+
+            IsParticipant runInstance = new IsParticipant();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+        }
+
+        @Override
+        public HashMap<String, Conference> getConferenceList() throws RemoteException {
+            // class ConfList extends SipRunnableWithReturn {
+            // @Override
+            // protected StringVect doRun() throws SameThreadException {
+            // Log.i(TAG, "SipService.getConferenceList() thread running...");
+            // return callManagerJNI.getConferenceList();
+            // }
+            // }
+            // ;
+            // ConfList runInstance = new ConfList();
+            // getExecutor().execute(runInstance);
+            // while (!runInstance.isDone()) {
+            // // Log.w(TAG, "Waiting for getConferenceList");
+            // }
+            // StringVect swigvect = (StringVect) runInstance.getVal();
+            //
+            // ArrayList<String> nativelist = new ArrayList<String>();
+            //
+            // for (int i = 0; i < swigvect.size(); i++)
+            // nativelist.add(swigvect.get(i));
+            //
+            // return nativelist;
+            return mConferences;
+        }
+
+        @Override
+        public List getParticipantList(final String confID) throws RemoteException {
+            class PartList extends SipRunnableWithReturn {
+                @Override
+                protected StringVect doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getParticipantList() thread running...");
+                    return Ringservice.sflph_call_get_participant_list(confID);
+                }
+            }
+            ;
+            PartList runInstance = new PartList();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+                Log.w(TAG, "getParticipantList");
+            }
+            StringVect swigvect = (StringVect) runInstance.getVal();
+            Log.w(TAG, "After that");
+            ArrayList<String> nativelist = new ArrayList<String>();
+
+            for (int i = 0; i < swigvect.size(); i++)
+                nativelist.add(swigvect.get(i));
+
+            return nativelist;
+        }
+
+        @Override
+        public String getConferenceId(String callID) throws RemoteException {
+            Log.e(TAG, "getConferenceList not implemented");
+            return null;
+        }
+
+        @Override
+        public String getConferenceDetails(final String callID) throws RemoteException {
+            class ConfDetails extends SipRunnableWithReturn {
+                @Override
+                protected StringMap doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getConferenceDetails() thread running...");
+                    return Ringservice.sflph_call_get_conference_details(callID);
+                }
+            }
+            ConfDetails runInstance = new ConfDetails();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+                // Log.w(TAG, "Waiting for getConferenceList");
+            }
+            StringMap swigvect = (StringMap) runInstance.getVal();
+
+            return swigvect.get("CONF_STATE");
+        }
+
+        @Override
+        public String getRecordPath() throws RemoteException {
+            class RecordPath extends SipRunnableWithReturn {
+
+                @Override
+                protected String doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getRecordPath() thread running...");
+                    return Ringservice.sflph_config_get_record_path();
+                }
+            }
+
+            RecordPath runInstance = new RecordPath();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+                // Log.w(TAG, "Waiting for getRecordPath");
+            }
+
+            return (String) runInstance.getVal();
+        }
+
+        @Override
+        public boolean toggleRecordingCall(final String id) throws RemoteException {
+
+            class ToggleRecording extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.toggleRecordingCall() thread running...");
+                    boolean result = Ringservice.sflph_call_toggle_recording(id);
+
+                    if (getConferences().containsKey(id)) {
+                        getConferences().get(id).setRecording(result);
+                    } else {
+                        for (Conference c : getConferences().values()) {
+                            if (c.getCallById(id) != null)
+                                c.getCallById(id).setRecording(result);
+                        }
+                    }
+                    return result;
+                }
+            }
+
+            ToggleRecording runInstance = new ToggleRecording();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+
+        }
+
+        @Override
+        public boolean startRecordedFilePlayback(final String filepath) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.setRecordingCall() thread running...");
+                    Ringservice.sflph_call_play_recorded_file(filepath);
+                }
+            });
+            return false;
+        }
+
+        @Override
+        public void stopRecordedFilePlayback(final String filepath) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.stopRecordedFilePlayback() thread running...");
+                    Ringservice.sflph_call_stop_recorded_file(filepath);
+                }
+            });
+        }
+
+        @Override
+        public void setRecordPath(final String path) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.setRecordPath() " + path + " thread running...");
+                    Ringservice.sflph_config_set_record_path(path);
+                }
+            });
+        }
+
+        @Override
+        public void sendTextMessage(final String callID, final SipMessage message) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.sendTextMessage() thread running...");
+                    Ringservice.sflph_call_send_text_message(callID, message.comment);
+                    if (getConferences().get(callID) != null)
+                        getConferences().get(callID).addSipMessage(message);
+                }
+            });
+
+        }
+
+        @Override
+        public List getAudioCodecList(final String accountID) throws RemoteException {
+            class AudioCodecList extends SipRunnableWithReturn {
+
+                @Override
+                protected ArrayList<Codec> doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getAudioCodecList() thread running...");
+                    ArrayList<Codec> results = new ArrayList<Codec>();
+
+                    IntVect active_payloads = Ringservice.sflph_config_get_active_audio_codec_list(accountID);
+                    for (int i = 0; i < active_payloads.size(); ++i) {
+
+                        results.add(new Codec(active_payloads.get(i), Ringservice.sflph_config_get_audio_codec_details(active_payloads.get(i)), true));
+
+                    }
+                    IntVect payloads = Ringservice.sflph_config_get_audio_codec_list();
+
+                    for (int i = 0; i < payloads.size(); ++i) {
+                        boolean isActive = false;
+                        for (Codec co : results) {
+                            if (co.getPayload().toString().contentEquals(String.valueOf(payloads.get(i))))
+                                isActive = true;
+
+                        }
+                        if (isActive)
+                            continue;
+                        else
+                            results.add(new Codec(payloads.get(i), Ringservice.sflph_config_get_audio_codec_details(payloads.get(i)), false));
+
+                    }
+
+                    return results;
+                }
+            }
+
+            AudioCodecList runInstance = new AudioCodecList();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            return (ArrayList<Codec>) runInstance.getVal();
+        }
+
+        @Override
+        public Map getRingtoneList() throws RemoteException {
+            class RingtoneList extends SipRunnableWithReturn {
+
+                @Override
+                protected StringMap doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getRingtoneList() thread running...");
+                    return Ringservice.sflph_config_get_ringtone_list();
+                }
+            }
+
+            RingtoneList runInstance = new RingtoneList();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            StringMap ringtones = (StringMap) runInstance.getVal();
+
+            for (int i = 0; i < ringtones.size(); ++i) {
+                // Log.i(TAG,"ringtones "+i+" "+ ringtones.);
+            }
+
+            return null;
+        }
+
+        @Override
+        public boolean checkForPrivateKey(final String pemPath) throws RemoteException {
+            class hasPrivateKey extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.isCaptureMuted() thread running...");
+                    return Ringservice.sflph_config_check_for_private_key(pemPath);
+                }
+            }
+
+            hasPrivateKey runInstance = new hasPrivateKey();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+        }
+
+        @Override
+        public boolean checkCertificateValidity(final String pemPath) throws RemoteException {
+            class isValid extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.isCaptureMuted() thread running...");
+                    return Ringservice.sflph_config_check_certificate_validity(pemPath, pemPath);
+                }
+            }
+
+            isValid runInstance = new isValid();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+        }
+
+        @Override
+        public boolean checkHostnameCertificate(final String certificatePath, final String host, final String port) throws RemoteException {
+            class isValid extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.isCaptureMuted() thread running...");
+                    return Ringservice.sflph_config_check_hostname_certificate(host, port);
+                }
+            }
+
+            isValid runInstance = new isValid();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+        }
+
+        @Override
+        public void setActiveCodecList(final List codecs, final String accountID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.setActiveAudioCodecList() thread running...");
+                    StringVect list = new StringVect();
+                    for (Object codec : codecs) {
+                        list.add((String) codec);
+                    }
+                    Ringservice.sflph_config_set_active_audio_codec_list(list, accountID);
+                }
+            });
+        }
+
+
+        @Override
+        public Conference getCurrentCall() throws RemoteException {
+            for (Conference conf : mConferences.values()) {
+                if (conf.isIncoming())
+                    return conf;
+            }
+
+            for (Conference conf : mConferences.values()) {
+                if (conf.isOnGoing())
+                    return conf;
+            }
+
+            return null;
+        }
+
+        @Override
+        public void playDtmf(final String key) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.playDtmf() thread running...");
+                    Ringservice.sflph_call_play_dtmf(key);
+                }
+            });
+        }
+
+        @Override
+        public List getConcurrentCalls() throws RemoteException {
+            return new ArrayList(mConferences.values());
+        }
+
+        @Override
+        public Conference getConference(String id) throws RemoteException {
+            return mConferences.get(id);
+        }
+
+        @Override
+        public void setMuted(final boolean mute) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.setMuted() thread running...");
+                    Ringservice.sflph_config_mute_capture(mute);
+                }
+            });
+        }
+
+        @Override
+        public boolean isCaptureMuted() throws RemoteException {
+            class IsMuted extends SipRunnableWithReturn {
+
+                @Override
+                protected Boolean doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.isCaptureMuted() thread running...");
+                    return Ringservice.sflph_config_is_capture_muted();
+                }
+            }
+
+            IsMuted runInstance = new IsMuted();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+
+            return (Boolean) runInstance.getVal();
+        }
+
+        @Override
+        public void confirmSAS(final String callID) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.confirmSAS() thread running...");
+                    SecureSipCall call = (SecureSipCall) getCallById(callID);
+                    call.setSASConfirmed(true);
+                    Ringservice.sflph_call_set_sas_verified(callID);
+                }
+            });
+        }
+
+
+        @Override
+        public List getTlsSupportedMethods(){
+            class TlsMethods extends SipRunnableWithReturn {
+
+                @Override
+                protected List doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getCredentials() thread running...");
+                    StringVect map = Ringservice.sflph_config_get_supported_tls_method();
+                    return SwigNativeConverter.convertSwigToNative(map);
+                }
+            }
+
+            TlsMethods runInstance = new TlsMethods();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            return (List) runInstance.getVal();
+        }
+
+        @Override
+        public List getCredentials(final String accountID) throws RemoteException {
+            class Credentials extends SipRunnableWithReturn {
+
+                @Override
+                protected List doRun() throws SameThreadException {
+                    Log.i(TAG, "SipService.getCredentials() thread running...");
+                    VectMap map = Ringservice.sflph_config_get_credentials(accountID);
+                    return SwigNativeConverter.convertCredentialsToNative(map);
+                }
+            }
+
+            Credentials runInstance = new Credentials();
+            getExecutor().execute(runInstance);
+            while (!runInstance.isDone()) {
+            }
+            return (List) runInstance.getVal();
+        }
+
+        @Override
+        public void setCredentials(final String accountID, final List creds) throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.setCredentials() thread running...");
+                    ArrayList<HashMap<String, String>> list = (ArrayList<HashMap<String, String>>) creds;
+                    Ringservice.sflph_config_set_credentials(accountID, SwigNativeConverter.convertFromNativeToSwig(creds));
+                }
+            });
+        }
+
+        @Override
+        public void registerAllAccounts() throws RemoteException {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    Log.i(TAG, "SipService.registerAllAccounts() thread running...");
+                    Ringservice.sflph_config_register_all_accounts();
+                }
+            });
+        }
+
+        @Override
+        public void toggleSpeakerPhone(boolean toggle) throws RemoteException {
+            if (toggle)
+                mMediaManager.RouteToSpeaker();
+            else
+                mMediaManager.RouteToInternalSpeaker();
+        }
+
+    };
+
+    private void removeCall(String callID) {
+        Conference conf = findConference(callID);
+        if(conf == null)
+            return;
+        if(conf.getParticipants().size() == 1)
+            getConferences().remove(conf.getId());
+        else
+            conf.removeParticipant(conf.getCallById(callID));
+    }
+
+    protected Conference findConference(String callID) {
+        Conference result = null;
+        if (getConferences().get(callID) != null) {
+            result = getConferences().get(callID);
+        } else {
+            for (Entry<String, Conference> stringConferenceEntry : getConferences().entrySet()) {
+                Conference tmp = stringConferenceEntry.getValue();
+                for (SipCall c : tmp.getParticipants()) {
+                    if (c.getCallId().contentEquals(callID)) {
+                        result = tmp;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/ring-android/src/cx/ring/utils/AccelerometerListener.java b/ring-android/src/cx/ring/utils/AccelerometerListener.java
new file mode 100644
index 0000000..f2bbca7
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/AccelerometerListener.java
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Regis Montoya <r3gis.3R@gmail.com>
+ *  Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Message;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class is used to listen to the accelerometer to monitor the orientation of the phone. The client of this class is notified when the
+ * orientation changes between horizontal and vertical.
+ */
+public final class AccelerometerListener {
+    private SensorManager mSensorManager;
+    private Sensor mSensor;
+
+    // mOrientation is the orientation value most recently reported to the client.
+    private int mOrientation;
+
+    // mPendingOrientation is the latest orientation computed based on the sensor value.
+    // This is sent to the client after a rebounce delay, at which point it is copied to
+    // mOrientation.
+    private int mPendingOrientation;
+
+    private OrientationListener mListener;
+
+    // Device orientation
+    public static final int ORIENTATION_UNKNOWN = 0;
+    public static final int ORIENTATION_VERTICAL = 1;
+    public static final int ORIENTATION_HORIZONTAL = 2;
+
+    private static final int ORIENTATION_CHANGED = 1234;
+
+    private static final int VERTICAL_DEBOUNCE = 100;
+    private static final int HORIZONTAL_DEBOUNCE = 500;
+    private static final double VERTICAL_ANGLE = 50.0;
+
+    public interface OrientationListener {
+        public void orientationChanged(int orientation);
+    }
+
+    public AccelerometerListener(Context context, OrientationListener listener) {
+        mListener = listener;
+        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void enable(boolean enable) {
+//        if (DEBUG)
+//            Log.d(TAG, "enable(" + enable + ")");
+        synchronized (this) {
+            if (enable) {
+                mOrientation = ORIENTATION_UNKNOWN;
+                mPendingOrientation = ORIENTATION_UNKNOWN;
+                mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
+            } else {
+                mSensorManager.unregisterListener(mSensorListener);
+                mHandler.removeMessages(ORIENTATION_CHANGED);
+            }
+        }
+    }
+
+    private void setOrientation(int orientation) {
+        synchronized (this) {
+            if (mPendingOrientation == orientation) {
+                // Pending orientation has not changed, so do nothing.
+                return;
+            }
+
+            // Cancel any pending messages.
+            // We will either start a new timer or cancel alltogether
+            // if the orientation has not changed.
+            mHandler.removeMessages(ORIENTATION_CHANGED);
+
+            if (mOrientation != orientation) {
+                // Set timer to send an event if the orientation has changed since its
+                // previously reported value.
+                mPendingOrientation = orientation;
+                Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
+                // set delay to our debounce timeout
+                int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE : HORIZONTAL_DEBOUNCE);
+                mHandler.sendMessageDelayed(m, delay);
+            } else {
+                // no message is pending
+                mPendingOrientation = ORIENTATION_UNKNOWN;
+            }
+        }
+    }
+
+    private void onSensorEvent(double x, double y, double z) {
+//        if (VDEBUG)
+//            Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
+
+        // If some values are exactly zero, then likely the sensor is not powered up yet.
+        // ignore these events to avoid false horizontal positives.
+        if (x == 0.0 || y == 0.0 || z == 0.0)
+            return;
+
+        // magnitude of the acceleration vector projected onto XY plane
+        double xy = Math.sqrt(x * x + y * y);
+        // compute the vertical angle
+        double angle = Math.atan2(xy, z);
+        // convert to degrees
+        angle = angle * 180.0 / Math.PI;
+        int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
+//        if (VDEBUG)
+//            Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
+        setOrientation(orientation);
+    }
+
+    SensorEventListener mSensorListener = new SensorEventListener() {
+        public void onSensorChanged(SensorEvent event) {
+            onSensorEvent(event.values[0], event.values[1], event.values[2]);
+        }
+
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // ignore
+        }
+    };
+
+    Handler mHandler = new AccelerometerHandler(this);
+
+    private static class AccelerometerHandler extends Handler {
+        WeakReference<AccelerometerListener> l;
+
+        AccelerometerHandler(AccelerometerListener listener) {
+            l = new WeakReference<AccelerometerListener>(listener);
+        }
+
+        public void handleMessage(Message msg) {
+            AccelerometerListener listener = l.get();
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+            case ORIENTATION_CHANGED:
+                synchronized (listener) {
+                    listener.mOrientation = listener.mPendingOrientation;
+//                    if (DEBUG) {
+//                        Log.d(TAG, "orientation: "
+//                                + (listener.mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
+//                                        : (listener.mOrientation == ORIENTATION_VERTICAL ? "vertical" : "unknown")));
+//                    }
+                    listener.mListener.orientationChanged(listener.mOrientation);
+                }
+                break;
+            }
+        }
+    };
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/utils/CallProximityManager.java b/ring-android/src/cx/ring/utils/CallProximityManager.java
new file mode 100644
index 0000000..509aa42
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/CallProximityManager.java
@@ -0,0 +1,315 @@
+/*
+ *  Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Regis Montoya <r3gis.3R@gmail.com>
+ *  Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+package cx.ring.utils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import cx.ring.utils.AccelerometerListener.OrientationListener;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.net.wifi.WifiManager;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * Class to manage proximity detection while in call.
+ * 
+ */
+public class CallProximityManager implements SensorEventListener, OrientationListener {
+    private static final String THIS_FILE = "CallProximityManager";
+
+    private Context mContext;
+
+    private SensorManager sensorManager;
+    private PowerManager powerManager;
+
+    // Timeout management of screen locker ui
+    // private ScreenLocker mScreenLocker;
+    private Boolean useTimeoutOverlay = null;
+
+    // Self management of proximity sensor
+    private Sensor proximitySensor;
+    private static final float PROXIMITY_THRESHOLD = 5.0f;
+    private boolean invertProximitySensor = false;
+    private boolean proximitySensorTracked = false;
+    private boolean isFirstRun = true;
+    private ProximityDirector mDirector = null;
+
+    // The hidden api that uses a wake lock
+    private WakeLock proximityWakeLock;
+
+    // The accelerometer
+    private AccelerometerListener accelerometerManager;
+    private int mOrientation;
+    private boolean accelerometerEnabled = false;
+
+    private int WAIT_FOR_PROXIMITY_NEGATIVE = 1;
+    // private final static int SCREEN_LOCKER_ACQUIRE_DELAY = "google_sdk".equals(Build.PRODUCT) ? ScreenLocker.WAIT_BEFORE_LOCK_LONG
+    // : ScreenLocker.WAIT_BEFORE_LOCK_SHORT;
+
+    private static Method powerLockReleaseIntMethod;
+
+    public interface ProximityDirector {
+        public boolean shouldActivateProximity();
+
+        public void onProximityTrackingChanged(boolean acquired);
+    }
+
+    public CallProximityManager(Context context, ProximityDirector director) {
+        mContext = context;
+        mDirector = director;
+        // mScreenLocker = screenLocker;
+
+        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        accelerometerManager = new AccelerometerListener(context, this);
+
+        // Try to detect the hidden api
+        if (powerManager != null) {
+            WifiManager wman = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+
+            // Try to use powermanager proximity sensor
+            try {
+                boolean supportProximity = false;
+                Field f = PowerManager.class.getDeclaredField("PROXIMITY_SCREEN_OFF_WAKE_LOCK");
+                int proximityScreenOffWakeLock = (Integer) f.get(null);
+                if (Compatibility.isCompatible(17)) {
+                    // Changes of the private API on android 4.2
+                    Method method = powerManager.getClass().getDeclaredMethod("isWakeLockLevelSupported", int.class);
+                    supportProximity = (Boolean) method.invoke(powerManager, proximityScreenOffWakeLock);
+                    Log.d(THIS_FILE, "Use 4.2 detection way for proximity sensor detection. Result is " + supportProximity);
+                } else {
+                    Method method = powerManager.getClass().getDeclaredMethod("getSupportedWakeLockFlags");
+                    int supportedFlags = (Integer) method.invoke(powerManager);
+                    Log.d(THIS_FILE, "Proxmity flags supported : " + supportedFlags);
+                    supportProximity = ((supportedFlags & proximityScreenOffWakeLock) != 0x0);
+                }
+                if (supportProximity) {
+                    Log.d(THIS_FILE, "We can use native screen locker !!");
+                    proximityWakeLock = powerManager.newWakeLock(proximityScreenOffWakeLock, "org.sflphone.CallProximity");
+                    proximityWakeLock.setReferenceCounted(false);
+                }
+
+            } catch (Exception e) {
+                Log.d(THIS_FILE, "Impossible to get power manager supported wake lock flags ");
+            }
+            if (powerLockReleaseIntMethod == null) {
+                try {
+                    powerLockReleaseIntMethod = proximityWakeLock.getClass().getDeclaredMethod("release", int.class);
+
+                } catch (Exception e) {
+                    Log.d(THIS_FILE, "Impossible to get power manager release with it");
+                }
+
+            }
+        }
+
+        // Try to detect a proximity sensor as fallback
+        if (proximityWakeLock == null) {
+            proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+            // invertProximitySensor = SipConfigManager.getPreferenceBooleanValue(context, SipConfigManager.INVERT_PROXIMITY_SENSOR);
+        }
+
+    }
+
+    public synchronized void startTracking() {
+        // If we should manage it ourselves
+        if (proximitySensor != null && !proximitySensorTracked) {
+            // Fall back to manual mode
+            isFirstRun = true;
+            Log.d(THIS_FILE, "Register sensor");
+            sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
+            proximitySensorTracked = true;
+        }
+        if (!accelerometerEnabled) {
+            accelerometerManager.enable(true);
+            accelerometerEnabled = true;
+        }
+    }
+
+    public synchronized void stopTracking() {
+        if (proximitySensor != null && proximitySensorTracked) {
+            proximitySensorTracked = false;
+            sensorManager.unregisterListener(this);
+            Log.d(THIS_FILE, "Unregister to sensor is done !!!");
+        }
+        if (accelerometerEnabled) {
+            accelerometerManager.enable(false);
+            accelerometerEnabled = false;
+        }
+        // mScreenLocker.tearDown();
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        // Log.d(THIS_FILE, "Tracked : "+proximitySensorTracked);
+        if (proximitySensorTracked && !isFirstRun) {
+            float distance = event.values[0];
+            boolean active = (distance >= 0.0 && distance < PROXIMITY_THRESHOLD && distance < event.sensor.getMaximumRange());
+            if (invertProximitySensor) {
+                active = !active;
+            }
+            Log.d(THIS_FILE, "Distance is now " + distance);
+
+            boolean isValidCallState = false;
+            if (mDirector != null) {
+                isValidCallState = mDirector.shouldActivateProximity();
+            }
+
+            if (isValidCallState && active) {
+                // mScreenLocker.show();
+                if (mDirector != null) {
+                    mDirector.onProximityTrackingChanged(true);
+                }
+            } else {
+                // mScreenLocker.hide();
+                if (mDirector != null) {
+                    mDirector.onProximityTrackingChanged(false);
+                }
+            }
+
+        }
+        if (isFirstRun) {
+            isFirstRun = false;
+        }
+    }
+
+    private boolean isProximityWakeHeld = false;
+
+    /**
+     * Release any lock taken by the proximity sensor
+     */
+    public synchronized void release(int flag) {
+        if (proximityWakeLock != null && isProximityWakeHeld) {
+            boolean usedNewRelease = false;
+            if (powerLockReleaseIntMethod != null) {
+                try {
+                    powerLockReleaseIntMethod.invoke(proximityWakeLock, flag);
+                    usedNewRelease = true;
+                    // Log.d(THIS_FILE, "CALL NEW RELEASE WITH FLAG " + flag);
+                } catch (Exception e) {
+                    Log.d(THIS_FILE, "Error calling new release method ", e);
+                }
+            }
+            if (!usedNewRelease) {
+                proximityWakeLock.release();
+            }
+            isProximityWakeHeld = false;
+        }
+
+        if (shouldUseTimeoutOverlay()) {
+            // mScreenLocker.hide();
+        }
+        // Notify
+        if (mDirector != null) {
+            mDirector.onProximityTrackingChanged(false);
+        }
+    }
+
+    public synchronized void acquire() {
+        if (proximityWakeLock != null && !isProximityWakeHeld) {
+            proximityWakeLock.acquire();
+            isProximityWakeHeld = true;
+        }
+        if (shouldUseTimeoutOverlay()) {
+            // mScreenLocker.delayedLock(SCREEN_LOCKER_ACQUIRE_DELAY);
+        }
+        // Notify
+        if (mDirector != null) {
+            mDirector.onProximityTrackingChanged(true);
+        }
+    }
+
+    /**
+     * Update proximity lock mode depending on current state
+     */
+    public synchronized void updateProximitySensorMode() {
+
+        // We do not keep the screen off when the user is outside in-call screen and we are
+        // horizontal, but we do not force it on when we become horizontal until the
+        // proximity sensor goes negative.
+        boolean horizontal = (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
+
+        boolean activeRegardingCalls = false;
+        if (mDirector != null) {
+            activeRegardingCalls = mDirector.shouldActivateProximity();
+        }
+
+        //Log.d(THIS_FILE, "Horizontal : " + horizontal + " and activate for calls " + activeRegardingCalls);
+        if (activeRegardingCalls && !horizontal) {
+            // Phone is in use! Arrange for the screen to turn off
+            // automatically when the sensor detects a close object.
+            acquire();
+        } else {
+            // Phone is either idle, or ringing. We don't want any
+            // special proximity sensor behavior in either case.
+            int flags = (!horizontal ? 0 : WAIT_FOR_PROXIMITY_NEGATIVE);
+            release(flags);
+        }
+    }
+
+    /**
+     * Should the application display the overlay after a timeout.
+     * 
+     * @return false if we are in table mode or if proximity sensor can be used
+     */
+    private boolean shouldUseTimeoutOverlay() {
+        if (useTimeoutOverlay == null) {
+            useTimeoutOverlay = proximitySensor == null && proximityWakeLock == null && !Compatibility.isTabletScreen(mContext);
+        }
+        return useTimeoutOverlay;
+    }
+
+    public void restartTimer() {
+        if (shouldUseTimeoutOverlay()) {
+            // mScreenLocker.delayedLock(ScreenLocker.WAIT_BEFORE_LOCK_LONG);
+        }
+    }
+
+    @Override
+    public void orientationChanged(int orientation) {
+        mOrientation = orientation;
+        updateProximitySensorMode();
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/utils/Compatibility.java b/ring-android/src/cx/ring/utils/Compatibility.java
new file mode 100644
index 0000000..b40cbfc
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/Compatibility.java
@@ -0,0 +1,475 @@
+/*
+ *  Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Regis Montoya <r3gis.3R@gmail.com>
+ *  Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.lang.reflect.Field;
+import java.util.regex.Pattern;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.MediaRecorder.AudioSource;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+@SuppressWarnings("deprecation")
+public final class Compatibility {
+
+    private Compatibility() {
+    }
+
+    private static final String THIS_FILE = "Compat";
+
+    public static int getApiLevel() {
+        return android.os.Build.VERSION.SDK_INT;
+    }
+
+    public static boolean isCompatible(int apiLevel) {
+        return android.os.Build.VERSION.SDK_INT >= apiLevel;
+    }
+
+    /**
+     * Get the stream id for in call track. Can differ on some devices. Current device for which it's different :
+     * 
+     * @return
+     */
+    public static int getInCallStream(boolean requestBluetooth) {
+        /* Archos 5IT */
+        if (android.os.Build.BRAND.equalsIgnoreCase("archos") && android.os.Build.DEVICE.equalsIgnoreCase("g7a")) {
+            // Since archos has no voice call capabilities, voice call stream is
+            // not implemented
+            // So we have to choose the good stream tag, which is by default
+            // falled back to music
+            return AudioManager.STREAM_MUSIC;
+        }
+        if (requestBluetooth) {
+            return 6; /* STREAM_BLUETOOTH_SCO -- Thx @Stefan for the contrib */
+        }
+
+        // return AudioManager.STREAM_MUSIC;
+        return AudioManager.STREAM_VOICE_CALL;
+    }
+
+    public static boolean shouldUseRoutingApi() {
+        Log.d(THIS_FILE, "Current device " + android.os.Build.BRAND + " - " + android.os.Build.DEVICE);
+
+        // HTC evo 4G
+        if (android.os.Build.PRODUCT.equalsIgnoreCase("htc_supersonic")) {
+            return true;
+        }
+
+        // ZTE joe
+        if (android.os.Build.DEVICE.equalsIgnoreCase("joe")) {
+            return true;
+        }
+
+        // Samsung GT-S5830
+        return android.os.Build.DEVICE.toUpperCase().startsWith("GT-S");
+    }
+
+    public static boolean shouldUseModeApi() {
+
+        // ZTE blade et joe
+        if (android.os.Build.DEVICE.equalsIgnoreCase("blade") || android.os.Build.DEVICE.equalsIgnoreCase("joe")) {
+            return true;
+        }
+        // Samsung GT-S5360 GT-S5830 GT-S6102 ... probably all..
+        if (android.os.Build.DEVICE.toUpperCase().startsWith("GT-") || android.os.Build.PRODUCT.toUpperCase().startsWith("GT-")
+                || android.os.Build.DEVICE.toUpperCase().startsWith("YP-")) {
+            return true;
+        }
+
+        // HTC evo 4G
+        if (android.os.Build.PRODUCT.equalsIgnoreCase("htc_supersonic")) {
+            return true;
+        }
+        // LG P500, Optimus V
+        if (android.os.Build.DEVICE.toLowerCase().startsWith("thunder")) {
+            return true;
+        }
+        // LG-E720(b)
+        if (android.os.Build.MODEL.toUpperCase().startsWith("LG-E720") && !Compatibility.isCompatible(9)) {
+            return true;
+        }
+        // LG-LS840
+        if (android.os.Build.DEVICE.toLowerCase().startsWith("cayman")) {
+            return true;
+        }
+
+        // Huawei
+        if (android.os.Build.DEVICE.equalsIgnoreCase("U8150") || android.os.Build.DEVICE.equalsIgnoreCase("U8110")
+                || android.os.Build.DEVICE.equalsIgnoreCase("U8120") || android.os.Build.DEVICE.equalsIgnoreCase("U8100")
+                || android.os.Build.PRODUCT.equalsIgnoreCase("U8655")) {
+            return true;
+        }
+
+        // Moto defy mini
+        if (android.os.Build.MODEL.equalsIgnoreCase("XT320")) {
+            return true;
+        }
+
+        // Alcatel
+        if (android.os.Build.DEVICE.toUpperCase().startsWith("ONE_TOUCH_993D")) {
+            return true;
+        }
+
+        // N4
+        return android.os.Build.DEVICE.toUpperCase().startsWith("MAKO");
+
+    }
+
+    public static String guessInCallMode() {
+        // New api for 2.3.3 is not available on galaxy S II :(
+        if (!isCompatible(11) && android.os.Build.DEVICE.toUpperCase().startsWith("GT-I9100")) {
+            return Integer.toString(AudioManager.MODE_NORMAL);
+        }
+
+        if (android.os.Build.BRAND.equalsIgnoreCase("sdg") || isCompatible(10)) {
+            // Note that in APIs this is only available from level 11.
+            return "3";
+        }
+        if (android.os.Build.DEVICE.equalsIgnoreCase("blade")) {
+            return Integer.toString(AudioManager.MODE_IN_CALL);
+        }
+
+        if (!isCompatible(5)) {
+            return Integer.toString(AudioManager.MODE_IN_CALL);
+        }
+
+        return Integer.toString(AudioManager.MODE_NORMAL);
+    }
+
+    public static String getDefaultMicroSource() {
+        // Except for galaxy S II :(
+        if (!isCompatible(11) && android.os.Build.DEVICE.toUpperCase().startsWith("GT-I9100")) {
+            return Integer.toString(AudioSource.MIC);
+        }
+
+        if (isCompatible(10)) {
+            // Note that in APIs this is only available from level 11.
+            // VOICE_COMMUNICATION
+            return Integer.toString(0x7);
+        }
+        /*
+         * Too risky in terms of regressions else if (isCompatible(4)) { // VOICE_CALL return 0x4; }
+         */
+        /*
+         * if(android.os.Build.MODEL.equalsIgnoreCase("X10i")) { // VOICE_CALL return Integer.toString(0x4); }
+         */
+        /*
+         * Not relevant anymore, atrix I tested sounds fine with that if(android.os.Build.DEVICE.equalsIgnoreCase("olympus")) { //Motorola atrix bug
+         * // CAMCORDER return Integer.toString(0x5); }
+         */
+
+        return Integer.toString(AudioSource.DEFAULT);
+    }
+
+    public static String getDefaultFrequency() {
+        if (android.os.Build.DEVICE.equalsIgnoreCase("olympus")) {
+            // Atrix bug
+            return "32000";
+        }
+        if (android.os.Build.DEVICE.toUpperCase().equals("GT-P1010")) {
+            // Galaxy tab see issue 932
+            return "32000";
+        }
+
+        return isCompatible(4) ? "16000" : "8000";
+    }
+
+    public static String getCpuAbi() {
+        if (isCompatible(4)) {
+            Field field;
+            try {
+                field = android.os.Build.class.getField("CPU_ABI");
+                return field.get(null).toString();
+            } catch (Exception e) {
+                Log.w(THIS_FILE, "Announce to be android 1.6 but no CPU ABI field", e);
+            }
+
+        }
+        return "armeabi";
+    }
+
+    public final static int getNumCores() {
+        // Private Class to display only CPU devices in the directory listing
+        class CpuFilter implements FileFilter {
+            @Override
+            public boolean accept(File pathname) {
+                // Check if filename is "cpu", followed by a single digit number
+                if (Pattern.matches("cpu[0-9]", pathname.getName())) {
+                    return true;
+                }
+                return false;
+            }
+        }
+        try {
+            // Get directory containing CPU info
+            File dir = new File("/sys/devices/system/cpu/");
+            // Filter to only list the devices we care about
+            File[] files = dir.listFiles(new CpuFilter());
+            // Return the number of cores (virtual CPU devices)
+            return files.length;
+        } catch (Exception e) {
+            return Runtime.getRuntime().availableProcessors();
+        }
+    }
+
+    private static boolean needPspWorkaround() {
+        // New api for 2.3 does not work on Incredible S
+        if (android.os.Build.DEVICE.equalsIgnoreCase("vivo")) {
+            return true;
+        }
+
+        // New API for android 2.3 should be able to manage this but do only for
+        // honeycomb cause seems not correctly supported by all yet
+        if (isCompatible(11)) {
+            return false;
+        }
+
+        // All htc except....
+        if (android.os.Build.PRODUCT.toLowerCase().startsWith("htc") || android.os.Build.BRAND.toLowerCase().startsWith("htc")
+                || android.os.Build.PRODUCT.toLowerCase().equalsIgnoreCase("inc") /*
+                                                                                   * For Incredible
+                                                                                   */
+                || android.os.Build.DEVICE.equalsIgnoreCase("passion") /* N1 */) {
+            if (android.os.Build.DEVICE.equalsIgnoreCase("hero") /* HTC HERO */
+                    || android.os.Build.DEVICE.equalsIgnoreCase("magic") /*
+                                                                          * Magic Aka Dev G2
+                                                                          */
+                    || android.os.Build.DEVICE.equalsIgnoreCase("tatoo") /* Tatoo */
+                    || android.os.Build.DEVICE.equalsIgnoreCase("dream") /*
+                                                                          * Dream Aka Dev G1
+                                                                          */
+                    || android.os.Build.DEVICE.equalsIgnoreCase("legend") /* Legend */
+
+            ) {
+                return false;
+            }
+
+            // Older than 2.3 has no chance to have the new full perf wifi mode
+            // working since does not exists
+            if (!isCompatible(9)) {
+                return true;
+            } else {
+                // N1 is fine with that
+                if (android.os.Build.DEVICE.equalsIgnoreCase("passion")) {
+                    return false;
+                }
+                return true;
+            }
+
+        }
+        // Dell streak
+        if (android.os.Build.BRAND.toLowerCase().startsWith("dell") && android.os.Build.DEVICE.equalsIgnoreCase("streak")) {
+            return true;
+        }
+        // Motorola milestone 1 and 2 & motorola droid & defy not under 2.3
+        if ((android.os.Build.DEVICE.toLowerCase().contains("milestone2") || android.os.Build.BOARD.toLowerCase().contains("sholes")
+                || android.os.Build.PRODUCT.toLowerCase().contains("sholes") || android.os.Build.DEVICE.equalsIgnoreCase("olympus") || android.os.Build.DEVICE
+                .toLowerCase().contains("umts_jordan")) && !isCompatible(9)) {
+            return true;
+        }
+        // Moto defy mini
+        if (android.os.Build.MODEL.equalsIgnoreCase("XT320")) {
+            return true;
+        }
+
+        // Alcatel ONE touch
+        if (android.os.Build.DEVICE.startsWith("one_touch_990")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean needToneWorkaround() {
+        if (android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i5800") || android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i5801")
+                || android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i9003")) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean needSGSWorkaround() {
+        if (isCompatible(9)) {
+            return false;
+        }
+        if (android.os.Build.DEVICE.toUpperCase().startsWith("GT-I9000") || android.os.Build.DEVICE.toUpperCase().startsWith("GT-P1000")) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean needWebRTCImplementation() {
+        if (android.os.Build.DEVICE.toLowerCase().contains("droid2")) {
+            return true;
+        }
+        if (android.os.Build.MODEL.toLowerCase().contains("droid bionic")) {
+            return true;
+        }
+        if (android.os.Build.DEVICE.toLowerCase().contains("sunfire")) {
+            return true;
+        }
+        // Huawei Y300
+        if (android.os.Build.DEVICE.equalsIgnoreCase("U8833")) {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean shouldSetupAudioBeforeInit() {
+        // Setup for GT / GS samsung devices.
+        if (android.os.Build.DEVICE.toLowerCase().startsWith("gt-") || android.os.Build.PRODUCT.toLowerCase().startsWith("gt-")) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean shouldFocusAudio() {
+        /* HTC One X */
+        if (android.os.Build.DEVICE.toLowerCase().startsWith("endeavoru") || android.os.Build.DEVICE.toLowerCase().startsWith("evita")) {
+            return false;
+        }
+
+        if (android.os.Build.DEVICE.toUpperCase().startsWith("GT-P7510") && isCompatible(15)) {
+            return false;
+        }
+        return true;
+    }
+
+    // private static int getDefaultAudioImplementation() {
+    // // Acer A510
+    // if (android.os.Build.DEVICE.toLowerCase().startsWith("picasso")) {
+    // return SipConfigManager.AUDIO_IMPLEMENTATION_JAVA;
+    // }
+    // if (Compatibility.isCompatible(11)) {
+    // return SipConfigManager.AUDIO_IMPLEMENTATION_OPENSLES;
+    // }
+    // if (android.os.Build.DEVICE.equalsIgnoreCase("ST25i") && Compatibility.isCompatible(10)) {
+    // return SipConfigManager.AUDIO_IMPLEMENTATION_OPENSLES;
+    // }
+    // if (android.os.Build.DEVICE.equalsIgnoreCase("u8510") && Compatibility.isCompatible(10)) {
+    // return SipConfigManager.AUDIO_IMPLEMENTATION_OPENSLES;
+    // }
+    // return SipConfigManager.AUDIO_IMPLEMENTATION_JAVA;
+    // }
+
+    public static boolean useFlipAnimation() {
+        if (android.os.Build.BRAND.equalsIgnoreCase("archos") && android.os.Build.DEVICE.equalsIgnoreCase("g7a")) {
+            return false;
+        }
+        return true;
+    }
+
+    public static Intent getContactPhoneIntent() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        /*
+         * intent.setAction(Intent.ACTION_GET_CONTENT); intent.setType(Contacts.Phones.CONTENT_ITEM_TYPE);
+         */
+        // Don't use constant to allow backward compat simply
+        intent.setData(Uri.parse("content://com.android.contacts/contacts"));
+        return intent;
+    }
+
+    public static boolean isTabletScreen(Context ctxt) {
+        boolean isTablet = false;
+        if (!isCompatible(4)) {
+            return false;
+        }
+        Configuration cfg = ctxt.getResources().getConfiguration();
+        int screenLayoutVal = 0;
+        try {
+            Field f = Configuration.class.getDeclaredField("screenLayout");
+            screenLayoutVal = (Integer) f.get(cfg);
+        } catch (Exception e) {
+            return false;
+        }
+        int screenLayout = (screenLayoutVal & 0xF);
+        // 0xF = SCREENLAYOUT_SIZE_MASK but avoid 1.5 incompat doing that
+        if (screenLayout == 0x3 || screenLayout == 0x4) {
+            // 0x3 = SCREENLAYOUT_SIZE_LARGE but avoid 1.5 incompat doing that
+            // 0x4 = SCREENLAYOUT_SIZE_XLARGE but avoid 1.5 incompat doing that
+            isTablet = true;
+        }
+
+        return isTablet;
+    }
+
+    public static int getHomeMenuId() {
+        return 0x0102002c;
+        // return android.R.id.home;
+    }
+
+    public static boolean isInstalledOnSdCard(Context context) {
+        // check for API level 8 and higher
+        if (Compatibility.isCompatible(8)) {
+            PackageManager pm = context.getPackageManager();
+            try {
+                PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
+                ApplicationInfo ai = pi.applicationInfo;
+                return (ai.flags & 0x00040000 /*
+                                               * ApplicationInfo. FLAG_EXTERNAL_STORAGE
+                                               */) == 0x00040000 /*
+                                                                  * ApplicationInfo. FLAG_EXTERNAL_STORAGE
+                                                                  */;
+            } catch (NameNotFoundException e) {
+                // ignore
+            }
+        }
+
+        // check for API level 7 - check files dir
+        try {
+            String filesDir = context.getFilesDir().getAbsolutePath();
+            if (filesDir.startsWith("/data/")) {
+                return false;
+            } else if (filesDir.contains(Environment.getExternalStorageDirectory().getPath())) {
+                return true;
+            }
+        } catch (Throwable e) {
+            // ignore
+        }
+
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/utils/MediaManager.java b/ring-android/src/cx/ring/utils/MediaManager.java
new file mode 100644
index 0000000..e1abe4e
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/MediaManager.java
@@ -0,0 +1,149 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import cx.ring.service.SipService;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.os.Handler;
+import android.util.Log;
+import cx.ring.utils.bluetooth.BluetoothWrapper;
+
+public class MediaManager implements OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
+
+    private static final String TAG = MediaManager.class.getSimpleName();
+    private SipService mService;
+    private SettingsContentObserver mSettingsContentObserver;
+    AudioManager mAudioManager;
+    private Ringer ringer;
+    //Bluetooth related
+    private BluetoothWrapper bluetoothWrapper;
+
+    public MediaManager(SipService aService) {
+        mService = aService;
+        mSettingsContentObserver = new SettingsContentObserver(mService, new Handler());
+        mAudioManager = (AudioManager) aService.getSystemService(Context.AUDIO_SERVICE);
+        
+        ringer = new Ringer(aService);
+    }
+
+    public void startService() {
+        if(bluetoothWrapper == null) {
+            bluetoothWrapper = BluetoothWrapper.getInstance(mService);
+            bluetoothWrapper.setBluetoothChangeListener(this);
+            bluetoothWrapper.register();
+        }
+        mService.getApplicationContext().getContentResolver()
+                .registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver);
+    }
+
+    public void stopService() {
+        Log.i(TAG, "Remove media manager....");
+        mService.getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
+        if(bluetoothWrapper != null) {
+            bluetoothWrapper.unregister();
+            bluetoothWrapper.setBluetoothChangeListener(null);
+            bluetoothWrapper = null;
+        }
+    }
+
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
+    public void obtainAudioFocus(boolean requestSpeakerOn) {
+        mAudioManager.requestAudioFocus(this, Compatibility.getInCallStream(false), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+        if(bluetoothWrapper != null && bluetoothWrapper.canBluetooth()) {
+            Log.d(TAG, "Try to enable bluetooth");
+            bluetoothWrapper.setBluetoothOn(true);
+        } else if (requestSpeakerOn && !mAudioManager.isWiredHeadsetOn()){
+            RouteToSpeaker();
+        }
+    }
+
+    @Override
+    public void onAudioFocusChange(int arg0) {
+
+    }
+
+    public void abandonAudioFocus() {
+        mAudioManager.abandonAudioFocus(this);
+        if (mAudioManager.isSpeakerphoneOn()) {
+            mAudioManager.setSpeakerphoneOn(false);
+        }
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+    }
+
+    public void RouteToSpeaker() {
+        mAudioManager.setSpeakerphoneOn(true);
+    }
+
+    public void RouteToInternalSpeaker() {
+        mAudioManager.setSpeakerphoneOn(false);
+    }
+    
+    
+    /**
+     * Start ringing announce for a given contact.
+     * It will also focus audio for us.
+     * @param remoteContact the contact to ring for. May resolve the contact ringtone if any.
+     */
+    synchronized public void startRing(String remoteContact) {
+        
+        if(!ringer.isRinging()) {
+            ringer.ring(remoteContact, "USELESS");
+        }else {
+            Log.d(TAG, "Already ringing ....");
+        }
+        
+    }
+    
+    /**
+     * Stop all ringing. <br/>
+     * Warning, this will not unfocus audio.
+     */
+    synchronized public void stopRing() {
+        if(ringer.isRinging()) {
+            ringer.stopRing();
+        }
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int status) {
+        //setSoftwareVolume();
+        //broadcastMediaChanged();
+    }
+
+}
diff --git a/ring-android/src/cx/ring/utils/Ringer.java b/ring-android/src/cx/ring/utils/Ringer.java
new file mode 100644
index 0000000..1e2767d
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/Ringer.java
@@ -0,0 +1,184 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Vibrator;
+import android.util.Log;
+
+
+/**
+ * Ringer manager for the Phone app.
+ */
+public class Ringer {
+    private static final String THIS_FILE = "Ringer";
+   
+    private static final int VIBRATE_LENGTH = 1000; // ms
+    private static final int PAUSE_LENGTH = 1000; // ms
+
+    // Uri for the ringtone.
+    Uri customRingtoneUri;
+
+    Vibrator vibrator;
+    VibratorThread vibratorThread;
+    Context context;
+
+    public Ringer(Context aContext) {
+        context = aContext;
+        vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+    }
+
+    /**
+     * Starts the ringtone and/or vibrator. 
+     * 
+     */
+    public void ring(String remoteContact, String defaultRingtone) {
+        Log.d(THIS_FILE, "==> ring() called...");
+
+        synchronized (this) {
+
+            AudioManager audioManager =
+                (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+            
+            //Save ringtone at the begining in case we raise vol
+//            ringtone = getRingtone(remoteContact, defaultRingtone);
+            
+            //No ring no vibrate
+            int ringerMode = audioManager.getRingerMode();
+            if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+                Log.d(THIS_FILE, "skipping ring and vibrate because profile is Silent");
+                return;
+            }
+            
+            // Vibrate
+            int vibrateSetting = audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
+            Log.d(THIS_FILE, "v=" + vibrateSetting + " rm=" + ringerMode);
+            if (vibratorThread == null &&
+                    (vibrateSetting == AudioManager.VIBRATE_SETTING_ON || 
+                            ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
+                vibratorThread = new VibratorThread();
+                Log.d(THIS_FILE, "Starting vibrator...");
+                vibratorThread.start();
+            }
+
+            // Vibrate only
+            if (ringerMode == AudioManager.RINGER_MODE_VIBRATE ||
+                    audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0 ) {
+                Log.d(THIS_FILE, "skipping ring because profile is Vibrate OR because volume is zero");
+                return;
+            }
+
+        }
+    }
+
+    /**
+     * @return true if we're playing a ringtone and/or vibrating
+     *     to indicate that there's an incoming call.
+     *     ("Ringing" here is used in the general sense.  If you literally
+     *     need to know if we're playing a ringtone or vibrating, use
+     *     isRingtonePlaying() or isVibrating() instead.)
+     */
+    public boolean isRinging() {
+        return (vibratorThread != null);
+    }
+    
+    /**
+     * Stops the ringtone and/or vibrator if any of these are actually
+     * ringing/vibrating.
+     */
+    public void stopRing() {
+        synchronized (this) {
+            Log.d(THIS_FILE, "==> stopRing() called...");
+
+            stopVibrator();
+        }
+    }
+    
+        
+    private void stopVibrator() {
+
+        if (vibratorThread != null) {
+            vibratorThread.interrupt();
+            try {
+                vibratorThread.join(250); // Should be plenty long (typ.)
+            } catch (InterruptedException e) {
+            } // Best efforts (typ.)
+            vibratorThread = null;
+        }
+    }
+
+    public void updateRingerMode() {
+
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        synchronized (this) {
+            int ringerMode = audioManager.getRingerMode();
+            // Silent : stop everything
+            if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+                stopRing();
+                return;
+            }
+
+            // Vibrate
+            int vibrateSetting = audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
+            // If not already started restart it
+            if (vibratorThread == null && (vibrateSetting == AudioManager.VIBRATE_SETTING_ON || ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
+                vibratorThread = new VibratorThread();
+                vibratorThread.start();
+            }
+
+            // Vibrate only
+            if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
+                return;
+            }
+            
+        }
+    }
+
+    private class VibratorThread extends Thread {
+        public void run() {
+            try {
+                while (true) {
+                    vibrator.vibrate(VIBRATE_LENGTH);
+                    Thread.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
+                }
+            } catch (InterruptedException ex) {
+                Log.d(THIS_FILE, "Vibrator thread interrupt");
+            } finally {
+                vibrator.cancel();
+            }
+            Log.d(THIS_FILE, "Vibrator thread exiting");
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/utils/SettingsContentObserver.java b/ring-android/src/cx/ring/utils/SettingsContentObserver.java
new file mode 100644
index 0000000..018cf28
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/SettingsContentObserver.java
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import cx.ring.service.SipService;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.util.Log;
+
+public class SettingsContentObserver extends ContentObserver {
+    double previousVolume;
+    SipService context;
+    private static final String TAG = "Settings";
+
+    public SettingsContentObserver(SipService c, Handler handler) {
+        super(handler);
+        context=c;  
+        AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        previousVolume = audio.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+    }
+
+    @Override
+    public boolean deliverSelfNotifications() {
+        return super.deliverSelfNotifications();
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        super.onChange(selfChange);
+
+        AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        double currentVolume = audio.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+
+        double delta=previousVolume-currentVolume;
+
+        if(delta>0)
+        {
+            Log.d(TAG,"Decreased");
+            previousVolume=currentVolume;
+//            context.changeVolume(currentVolume);
+        }
+        else if(delta<0)
+        {
+            Log.d(TAG,"Increased");
+            previousVolume=currentVolume;
+//            context.changeVolume(currentVolume);
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/utils/SipNotifications.java b/ring-android/src/cx/ring/utils/SipNotifications.java
new file mode 100644
index 0000000..356552b
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/SipNotifications.java
@@ -0,0 +1,203 @@
+/**
+ *  Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Adrien Béraud <adrien.beraud@gmail.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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import java.util.HashMap;
+import java.util.Random;
+
+import cx.ring.R;
+import cx.ring.client.HomeActivity;
+import cx.ring.model.Conference;
+import cx.ring.model.SipCall;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
+import android.net.sip.SipProfile;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat.Builder;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+
+public class SipNotifications {
+
+    private final NotificationManager notificationManager;
+    private final Context context;
+
+    public static final String NOTIF_CREATION = "notif_creation";
+    public static final String NOTIF_DELETION = "notif_deletion";
+
+    private final int NOTIFICATION_ID = new Random().nextInt(1000);
+
+    public static final int REGISTER_NOTIF_ID = 1;
+    public static final int CALL_NOTIF_ID = REGISTER_NOTIF_ID + 1;
+    public static final int CALLLOG_NOTIF_ID = REGISTER_NOTIF_ID + 2;
+    public static final int MESSAGE_NOTIF_ID = REGISTER_NOTIF_ID + 3;
+    public static final int VOICEMAIL_NOTIF_ID = REGISTER_NOTIF_ID + 4;
+
+    private static boolean isInit = false;
+
+    public SipNotifications(Context aContext) {
+        context = aContext;
+        notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (!isInit) {
+            cancelAll();
+            cancelCalls();
+            isInit = true;
+        }
+
+    }
+
+    public void onServiceCreate() {
+
+    }
+
+    public void onServiceDestroy() {
+        // Make sure our notification is gone.
+        cancelAll();
+        cancelCalls();
+    }
+
+    // Calls
+    public void showNotificationForCall(SipCall callInfo) {
+        // TODO
+    }
+
+    public void showNotificationForVoiceMail(SipProfile acc, int numberOfMessages) {
+        // TODO
+    }
+
+    protected static CharSequence buildTickerMessage(Context context, String address, String body) {
+        String displayAddress = address;
+
+        StringBuilder buf = new StringBuilder(displayAddress == null ? "" : displayAddress.replace('\n', ' ').replace('\r', ' '));
+        buf.append(':').append(' ');
+
+        int offset = buf.length();
+
+        if (!TextUtils.isEmpty(body)) {
+            body = body.replace('\n', ' ').replace('\r', ' ');
+            buf.append(body);
+        }
+
+        SpannableString spanText = new SpannableString(buf.toString());
+        spanText.setSpan(new StyleSpan(Typeface.BOLD), 0, offset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        return spanText;
+    }
+
+    public final void cancelCalls() {
+        notificationManager.cancel(CALL_NOTIF_ID);
+    }
+
+    public final void cancelMissedCalls() {
+        notificationManager.cancel(CALLLOG_NOTIF_ID);
+    }
+
+    public final void cancelMessages() {
+        notificationManager.cancel(MESSAGE_NOTIF_ID);
+    }
+
+    public final void cancelVoicemails() {
+        notificationManager.cancel(VOICEMAIL_NOTIF_ID);
+    }
+
+    public final void cancelAll() {
+        cancelMessages();
+        cancelMissedCalls();
+        cancelVoicemails();
+    }
+
+    public void publishMissedCallNotification(Conference missedConf) {
+
+        CharSequence tickerText = context.getString(R.string.notif_missed_call_title);
+        long when = System.currentTimeMillis();
+
+        Builder nb = new NotificationCompat.Builder(context);
+        nb.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher));
+        nb.setSmallIcon(R.drawable.ic_action_call);
+
+        nb.setTicker(tickerText);
+        nb.setWhen(when);
+        nb.setContentTitle(context.getString(R.string.notif_missed_call_title));
+        nb.setContentText(context.getString(R.string.notif_missed_call_content, missedConf.getParticipants().get(0).getmContact().getmDisplayName()));
+        Intent notificationIntent = new Intent(context, HomeActivity.class);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        // notification.setLatestEventInfo(context, contentTitle,
+        // contentText, contentIntent);
+        nb.setOnlyAlertOnce(true);
+        nb.setContentIntent(contentIntent);
+
+        Notification notification = nb.build();
+        // We have to re-write content view because getNotification setLatestEventInfo implicitly
+        // notification.contentView = contentView;
+
+        // startForegroundCompat(CALL_NOTIF_ID, notification);
+        notificationManager.notify(CALL_NOTIF_ID, notification);
+    }
+
+    public void makeNotification(HashMap<String, SipCall> calls) {
+        if (calls.size() == 0) {
+            return;
+        }
+        Intent notificationIntent = new Intent(context, HomeActivity.class);
+        PendingIntent contentIntent = PendingIntent.getActivity(context, 007, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(NOTIFICATION_ID); // clear previous notifications.
+
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
+
+        builder.setContentIntent(contentIntent).setOngoing(true).setSmallIcon(R.drawable.ic_launcher)
+                .setContentTitle(calls.size() + " ongoing calls").setTicker("Pending calls").setWhen(System.currentTimeMillis()).setAutoCancel(false);
+        builder.setPriority(NotificationCompat.PRIORITY_MAX);
+        Notification n = builder.build();
+
+        nm.notify(NOTIFICATION_ID, n);
+    }
+
+    public void removeNotification() {
+        NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(NOTIFICATION_ID);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/utils/SwigNativeConverter.java b/ring-android/src/cx/ring/utils/SwigNativeConverter.java
new file mode 100644
index 0000000..748391b
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/SwigNativeConverter.java
@@ -0,0 +1,193 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import cx.ring.model.account.AccountDetailAdvanced;
+import cx.ring.model.account.AccountDetailBasic;
+import cx.ring.model.account.AccountDetailSrtp;
+import cx.ring.model.account.AccountDetailTls;
+import cx.ring.service.ServiceConstants;
+import cx.ring.service.StringMap;
+import cx.ring.service.StringVect;
+import cx.ring.service.VectMap;
+
+public class SwigNativeConverter {
+
+    /**
+     * Native to Swig
+     */
+
+    public static StringMap convertFromNativeToSwig(HashMap<String, String> nativemap) {
+        StringMap swigmap = new StringMap();
+
+        Set<String> keys = nativemap.keySet();
+        for (String key : keys) {
+            if (nativemap.get(key) == null) {
+                swigmap.set(key, "");
+            } else {
+                swigmap.set(key, nativemap.get(key));
+            }
+        }
+        return swigmap;
+    }
+
+    public static VectMap convertFromNativeToSwig(List creds) {
+        ArrayList<HashMap<String, String>> todecode = (ArrayList<HashMap<String, String>>) creds;
+        VectMap toReturn = new VectMap();
+
+        for (HashMap<String, String> aTodecode : todecode) {
+            StringMap entry = new StringMap();
+            entry.set(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD, aTodecode.get(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD));
+            entry.set(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME, aTodecode.get(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME));
+            entry.set(AccountDetailBasic.CONFIG_ACCOUNT_REALM, aTodecode.get(AccountDetailBasic.CONFIG_ACCOUNT_REALM));
+            toReturn.add(entry);
+        }
+        return toReturn;
+    }
+
+    private static String tryToGet(StringMap smap, String key) {
+        if (smap.has_key(key)) {
+            return smap.get(key);
+        } else {
+            return "";
+        }
+    }
+
+    public static HashMap<String, String> convertAccountToNative(StringMap swigmap) {
+        HashMap<String, String> nativemap = new HashMap<String, String>();
+
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_TYPE, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_TYPE));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_ENABLE, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_ENABLE));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_USERAGENT, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_USERAGENT));
+        nativemap.put(AccountDetailAdvanced.CONFIG_ACCOUNT_MAILBOX, swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_MAILBOX));
+        nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_AUTOANSWER, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_AUTOANSWER));
+
+		if (swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_TYPE).equals("SIP"))
+		{
+
+            nativemap.put(AccountDetailBasic.CONFIG_ACCOUNT_ROUTESET, swigmap.get(AccountDetailBasic.CONFIG_ACCOUNT_ROUTESET));
+			nativemap
+				.put(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_EXPIRE, swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_EXPIRE));
+			nativemap.put(AccountDetailAdvanced.CONFIG_LOCAL_INTERFACE, swigmap.get(AccountDetailAdvanced.CONFIG_LOCAL_INTERFACE));
+			nativemap.put(AccountDetailAdvanced.CONFIG_STUN_SERVER, swigmap.get(AccountDetailAdvanced.CONFIG_STUN_SERVER));
+			nativemap
+				.put(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATUS, swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATUS));
+			nativemap.put(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE,
+				swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE));
+			nativemap.put(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATE_DESC,
+				swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATE_DESC));
+			nativemap.put(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE, swigmap.get(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE));
+			nativemap.put(AccountDetailAdvanced.CONFIG_KEEP_ALIVE_ENABLED, swigmap.get(AccountDetailAdvanced.CONFIG_KEEP_ALIVE_ENABLED));
+			nativemap.put(AccountDetailAdvanced.CONFIG_LOCAL_PORT, swigmap.get(AccountDetailAdvanced.CONFIG_LOCAL_PORT));
+			nativemap.put(AccountDetailAdvanced.CONFIG_PUBLISHED_ADDRESS, swigmap.get(AccountDetailAdvanced.CONFIG_PUBLISHED_ADDRESS));
+			nativemap.put(AccountDetailAdvanced.CONFIG_PUBLISHED_PORT, swigmap.get(AccountDetailAdvanced.CONFIG_PUBLISHED_PORT));
+			nativemap.put(AccountDetailAdvanced.CONFIG_PUBLISHED_SAMEAS_LOCAL, swigmap.get(AccountDetailAdvanced.CONFIG_PUBLISHED_SAMEAS_LOCAL));
+			nativemap.put(AccountDetailAdvanced.CONFIG_RINGTONE_ENABLED, swigmap.get(AccountDetailAdvanced.CONFIG_RINGTONE_ENABLED));
+			nativemap.put(AccountDetailAdvanced.CONFIG_RINGTONE_PATH, swigmap.get(AccountDetailAdvanced.CONFIG_RINGTONE_PATH));
+			nativemap.put(AccountDetailAdvanced.CONFIG_STUN_ENABLE, swigmap.get(AccountDetailAdvanced.CONFIG_STUN_ENABLE));
+			nativemap.put(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MAX, swigmap.get(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MAX));
+			nativemap.put(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MIN, swigmap.get(AccountDetailAdvanced.CONFIG_AUDIO_PORT_MIN));
+
+			nativemap.put(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE, swigmap.get(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE));
+			nativemap.put(AccountDetailSrtp.CONFIG_SRTP_RTP_FALLBACK, swigmap.get(AccountDetailSrtp.CONFIG_SRTP_RTP_FALLBACK));
+			nativemap.put(AccountDetailSrtp.CONFIG_ZRTP_DISPLAY_SAS, swigmap.get(AccountDetailSrtp.CONFIG_ZRTP_DISPLAY_SAS));
+			nativemap.put(AccountDetailSrtp.CONFIG_ZRTP_DISPLAY_SAS_ONCE, swigmap.get(AccountDetailSrtp.CONFIG_ZRTP_DISPLAY_SAS_ONCE));
+			nativemap.put(AccountDetailSrtp.CONFIG_ZRTP_HELLO_HASH, swigmap.get(AccountDetailSrtp.CONFIG_ZRTP_HELLO_HASH));
+			nativemap.put(AccountDetailSrtp.CONFIG_ZRTP_NOT_SUPP_WARNING, swigmap.get(AccountDetailSrtp.CONFIG_ZRTP_NOT_SUPP_WARNING));
+			nativemap.put(AccountDetailSrtp.CONFIG_SRTP_ENABLE, swigmap.get(AccountDetailSrtp.CONFIG_SRTP_ENABLE));
+
+			nativemap.put(AccountDetailTls.CONFIG_TLS_CIPHERS, swigmap.get(AccountDetailTls.CONFIG_TLS_CIPHERS));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_LISTENER_PORT, swigmap.get(AccountDetailTls.CONFIG_TLS_LISTENER_PORT));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_METHOD, swigmap.get(AccountDetailTls.CONFIG_TLS_METHOD));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, swigmap.get(AccountDetailTls.CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_PASSWORD, swigmap.get(AccountDetailTls.CONFIG_TLS_PASSWORD));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE, swigmap.get(AccountDetailTls.CONFIG_TLS_PRIVATE_KEY_FILE));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, swigmap.get(AccountDetailTls.CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_SERVER_NAME, swigmap.get(AccountDetailTls.CONFIG_TLS_SERVER_NAME));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_VERIFY_CLIENT, swigmap.get(AccountDetailTls.CONFIG_TLS_VERIFY_CLIENT));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_VERIFY_SERVER, swigmap.get(AccountDetailTls.CONFIG_TLS_VERIFY_SERVER));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE, swigmap.get(AccountDetailTls.CONFIG_TLS_CERTIFICATE_FILE));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE, swigmap.get(AccountDetailTls.CONFIG_TLS_CA_LIST_FILE));
+			nativemap.put(AccountDetailTls.CONFIG_TLS_ENABLE, swigmap.get(AccountDetailTls.CONFIG_TLS_ENABLE));
+		}
+
+        return nativemap;
+    }
+
+    public static HashMap<String, String> convertCallDetailsToNative(StringMap swigmap) {
+
+        HashMap<String, String> entry = new HashMap<String, String>();
+
+        entry.put(ServiceConstants.call.CALL_TYPE, tryToGet(swigmap, ServiceConstants.call.CALL_TYPE));
+        entry.put(ServiceConstants.call.PEER_NUMBER, tryToGet(swigmap, ServiceConstants.call.PEER_NUMBER));
+        entry.put(ServiceConstants.call.DISPLAY_NAME, tryToGet(swigmap, ServiceConstants.call.DISPLAY_NAME));
+        entry.put(ServiceConstants.call.CALL_STATE, tryToGet(swigmap, ServiceConstants.call.CALL_STATE));
+        entry.put(ServiceConstants.call.CONF_ID, tryToGet(swigmap, ServiceConstants.call.CONF_ID));
+        entry.put(ServiceConstants.call.TIMESTAMP_START, tryToGet(swigmap, ServiceConstants.call.TIMESTAMP_START));
+        entry.put(ServiceConstants.call.ACCOUNTID, tryToGet(swigmap, ServiceConstants.call.ACCOUNTID));
+
+        return entry;
+    }
+
+    public static ArrayList<HashMap<String, String>> convertCredentialsToNative(VectMap map) {
+
+        ArrayList<HashMap<String, String>> toReturn = new ArrayList<HashMap<String, String>>();
+
+        for (int i = 0; i < map.size(); ++i) {
+            StringMap entry;
+            HashMap<String, String> nativeEntry = new HashMap<String, String>();
+            entry = map.get(i);
+            nativeEntry.put(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD, entry.get(AccountDetailBasic.CONFIG_ACCOUNT_PASSWORD));
+            nativeEntry.put(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME, entry.get(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME));
+            nativeEntry.put(AccountDetailBasic.CONFIG_ACCOUNT_REALM, entry.get(AccountDetailBasic.CONFIG_ACCOUNT_REALM));
+            toReturn.add(nativeEntry);
+        }
+        return toReturn;
+    }
+
+    public static ArrayList<String> convertSwigToNative(StringVect vector) {
+        ArrayList<String> toReturn = new ArrayList<String>();
+        for (int i = 0; i < vector.size(); ++i) {
+            toReturn.add(vector.get(i));
+        }
+        return toReturn;
+    }
+}
diff --git a/ring-android/src/cx/ring/utils/bluetooth/BluetoothUtils14.java b/ring-android/src/cx/ring/utils/bluetooth/BluetoothUtils14.java
new file mode 100644
index 0000000..c378347
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/bluetooth/BluetoothUtils14.java
@@ -0,0 +1,167 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * This file is part of CSipSimple.
+ *
+ *  CSipSimple 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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  CSipSimple 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 CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package cx.ring.utils.bluetooth;
+
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.Set;
+
+public class BluetoothUtils14 extends BluetoothWrapper {
+
+    private static String TAG = BluetoothUtils14.class.getSimpleName();
+    private AudioManager audioManager;
+    private boolean isBluetoothConnected = false;
+    
+    
+    @Override
+    public boolean isBTHeadsetConnected() {
+        return bluetoothAdapter != null && (bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED);
+    }
+
+    
+
+    private BroadcastReceiver mediaStateReceiver = new BroadcastReceiver() {
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.d(TAG, ">>> BT SCO state changed !!! ");
+            if(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(action)) {
+                int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR );
+                Log.d(TAG, "BT SCO state changed : " + status + " target is " + targetBt);
+                audioManager.setBluetoothScoOn(targetBt);
+
+                if(status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
+                    isBluetoothConnected = true;
+                }else if(status == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
+                    isBluetoothConnected = false;
+                }
+
+                if(btChangesListener != null) {
+                    btChangesListener.onBluetoothStateChanged(status);
+                }
+            }
+        }
+    };
+
+    protected BluetoothAdapter bluetoothAdapter;
+
+    @Override
+    public void setContext(Context aContext){
+        super.setContext(aContext);
+        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        if(bluetoothAdapter == null) {
+            try {
+                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+            }catch(RuntimeException e) {
+                Log.w(TAG, "Cant get default bluetooth adapter ", e);
+            }
+        }
+    }
+
+    public boolean canBluetooth() {
+        // Detect if any bluetooth a device is available for call
+        if (bluetoothAdapter == null) {
+            // Device does not support Bluetooth
+            return false;
+        }
+        boolean hasConnectedDevice = false;
+        //If bluetooth is on
+        if(bluetoothAdapter.isEnabled()) {
+
+            //We get all bounded bluetooth devices
+            // bounded is not enough, should search for connected devices....
+            Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
+            for(BluetoothDevice device : pairedDevices) {
+                BluetoothClass bluetoothClass = device.getBluetoothClass();
+                if (bluetoothClass != null) {
+                    int deviceClass = bluetoothClass.getDeviceClass();
+                    if(bluetoothClass.hasService(BluetoothClass.Service.RENDER) ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ) {
+                        //And if any can be used as a audio handset
+                        hasConnectedDevice = true;
+                        break;
+                    }
+                }
+            }
+        }
+        boolean retVal = hasConnectedDevice && audioManager.isBluetoothScoAvailableOffCall();
+        Log.d(TAG, "Can I do BT ? "+retVal);
+        return retVal;
+    }
+
+    private boolean targetBt = false;
+    public void setBluetoothOn(boolean on) {
+        Log.d(TAG, "Ask for "+on+" vs "+audioManager.isBluetoothScoOn());
+        targetBt = on;
+        if(on != isBluetoothConnected) {
+            // BT SCO connection state is different from required activation
+            if(on) {
+                // First we try to connect
+                Log.d(TAG, "BT SCO on >>>");
+                audioManager.startBluetoothSco();
+            }else {
+                Log.d(TAG, "BT SCO off >>>");
+                // We stop to use BT SCO
+                audioManager.setBluetoothScoOn(false);
+                // And we stop BT SCO connection
+                audioManager.stopBluetoothSco();
+            }
+        }else if(on != audioManager.isBluetoothScoOn()) {
+            // BT SCO is already in desired connection state
+            // we only have to use it
+            audioManager.setBluetoothScoOn(on);
+        }
+    }
+
+    public boolean isBluetoothOn() {
+        return isBluetoothConnected;
+    }
+
+    @SuppressWarnings("deprecation")
+    public void register() {
+        Log.d(TAG, "Register BT media receiver");
+        context.registerReceiver(mediaStateReceiver , new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
+    }
+
+    public void unregister() {
+        try {
+            Log.d(TAG, "Unregister BT media receiver");
+            context.unregisterReceiver(mediaStateReceiver);
+        }catch(Exception e) {
+            Log.w(TAG, "Failed to unregister media state receiver",e);
+        }
+    }
+}
diff --git a/ring-android/src/cx/ring/utils/bluetooth/BluetoothWrapper.java b/ring-android/src/cx/ring/utils/bluetooth/BluetoothWrapper.java
new file mode 100644
index 0000000..053db85
--- /dev/null
+++ b/ring-android/src/cx/ring/utils/bluetooth/BluetoothWrapper.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * This file is part of CSipSimple.
+ *
+ *  CSipSimple 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.
+ *  If you own a pjsip commercial license you can also redistribute it
+ *  and/or modify it under the terms of the GNU Lesser General Public License
+ *  as an android library.
+ *
+ *  CSipSimple 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 CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package cx.ring.utils.bluetooth;
+
+import android.content.Context;
+
+
+public abstract class BluetoothWrapper {
+
+    public interface BluetoothChangeListener {
+        void onBluetoothStateChanged(int status);
+    }
+
+
+    private static BluetoothWrapper instance;
+    protected Context context;
+
+    protected BluetoothChangeListener btChangesListener;
+
+    public static BluetoothWrapper getInstance(Context context) {
+        if (instance == null) {
+            instance = new BluetoothUtils14();
+            instance.setContext(context);
+        }
+
+        return instance;
+    }
+
+    protected BluetoothWrapper() {
+    }
+
+    protected void setContext(Context ctxt) {
+        context = ctxt;
+    }
+
+    public void setBluetoothChangeListener(BluetoothChangeListener l) {
+        btChangesListener = l;
+    }
+
+    public abstract boolean canBluetooth();
+
+    public abstract void setBluetoothOn(boolean on);
+
+    public abstract boolean isBluetoothOn();
+
+    public abstract void register();
+
+    public abstract void unregister();
+
+    public abstract boolean isBTHeadsetConnected();
+}
diff --git a/ring-android/src/cx/ring/views/CallPaneLayout.java b/ring-android/src/cx/ring/views/CallPaneLayout.java
new file mode 100644
index 0000000..c9bd686
--- /dev/null
+++ b/ring-android/src/cx/ring/views/CallPaneLayout.java
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@gmail.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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import cx.ring.fragments.CallFragment;
+
+import android.content.Context;
+import android.support.v4.widget.SlidingPaneLayout;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class CallPaneLayout extends SlidingPaneLayout
+{
+	private CallFragment curFragment = null;
+
+	public CallFragment getCurFragment() {
+        return curFragment;
+    }
+
+    public void setCurFragment(CallFragment curFragment) {
+        this.curFragment = curFragment;
+    }
+
+    public CallPaneLayout(Context context, AttributeSet attrs)
+	{
+		super(context, attrs);
+	}
+
+	public CallPaneLayout(Context context, AttributeSet attrs, int defStyle)
+	{
+		super(context, attrs, defStyle);
+	}
+
+	@Override
+	public boolean onInterceptTouchEvent(MotionEvent event)
+	{
+		if(curFragment!=null && !curFragment.canOpenIMPanel()) {
+			return false;
+		}
+
+		return super.onInterceptTouchEvent(event);
+	}
+
+}
diff --git a/ring-android/src/cx/ring/views/CircularImageView.java b/ring-android/src/cx/ring/views/CircularImageView.java
new file mode 100644
index 0000000..ad5e73c
--- /dev/null
+++ b/ring-android/src/cx/ring/views/CircularImageView.java
@@ -0,0 +1,182 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class CircularImageView extends ImageView
+{
+    private int borderWidth = 4;
+    private int viewWidth;
+    private int viewHeight;
+    private Bitmap image;
+    private Paint paint;
+    private Paint paintBorder;
+    private BitmapShader shader;
+
+    public CircularImageView(Context context)
+    {
+        super(context);
+        setup();
+    }
+
+    public CircularImageView(Context context, AttributeSet attrs)
+    {
+        super(context, attrs);
+        setup();
+    }
+
+    public CircularImageView(Context context, AttributeSet attrs, int defStyle)
+    {
+        super(context, attrs, defStyle);
+        setup();
+    }
+
+    private void setup()
+    {
+        // init paint
+        paint = new Paint();
+        paint.setAntiAlias(true);
+
+        paintBorder = new Paint();
+        setBorderColor(Color.WHITE);
+        paintBorder.setAntiAlias(true);
+        this.setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
+        paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
+    }
+
+    public void setBorderWidth(int borderWidth)
+    {
+        this.borderWidth = borderWidth;
+        this.invalidate();
+    }
+
+    public void setBorderColor(int borderColor)
+    {
+        if (paintBorder != null)
+            paintBorder.setColor(borderColor);
+
+        this.invalidate();
+    }
+
+    private void loadBitmap()
+    {
+        BitmapDrawable bitmapDrawable = (BitmapDrawable) this.getDrawable();
+
+        if (bitmapDrawable != null)
+            image = bitmapDrawable.getBitmap();
+    }
+
+    @SuppressLint("DrawAllocation")
+    @Override
+    public void onDraw(Canvas canvas)
+    {
+        // load the bitmap
+        loadBitmap();
+
+        // init shader
+        if (image != null)
+        {
+            shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvas.getWidth(), canvas.getHeight(), false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            paint.setShader(shader);
+            int circleCenter = viewWidth / 2;
+
+            // circleCenter is the x or y of the view's center
+            // radius is the radius in pixels of the cirle to be drawn
+            // paint contains the shader that will texture the shape
+            canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, circleCenter + borderWidth - 4.0f, paintBorder);
+            canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, circleCenter - 4.0f, paint);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+    {
+        int width = measureWidth(widthMeasureSpec);
+        int height = measureHeight(heightMeasureSpec, widthMeasureSpec);
+
+        viewWidth = width - (borderWidth * 2);
+        viewHeight = height - (borderWidth * 2);
+
+        setMeasuredDimension(width, height);
+    }
+
+    private int measureWidth(int measureSpec)
+    {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+
+        if (specMode == MeasureSpec.EXACTLY)
+        {
+            // We were told how big to be
+            result = specSize;
+        }
+        else
+        {
+            // Measure the text
+            result = viewWidth;
+        }
+
+        return result;
+    }
+
+    private int measureHeight(int measureSpecHeight, int measureSpecWidth)
+    {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpecHeight);
+        int specSize = MeasureSpec.getSize(measureSpecHeight);
+
+        if (specMode == MeasureSpec.EXACTLY)
+        {
+            // We were told how big to be
+            result = specSize;
+        }
+        else
+        {
+            // Measure the text (beware: ascent is a negative number)
+            result = viewHeight;
+        }
+
+        return (result + 2);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/views/ClearableEditText.java b/ring-android/src/cx/ring/views/ClearableEditText.java
new file mode 100644
index 0000000..9527fd0
--- /dev/null
+++ b/ring-android/src/cx/ring/views/ClearableEditText.java
@@ -0,0 +1,161 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import cx.ring.R;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.widget.TextView.OnEditorActionListener;
+
+
+
+public class ClearableEditText extends RelativeLayout {
+    LayoutInflater inflater = null;
+    EditText edit_text;
+    Button btn_clear;
+    private TextWatcher watch = null;
+
+    public ClearableEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initViews();
+    }
+
+    public ClearableEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initViews();
+    }
+
+    public ClearableEditText(Context context) {
+        super(context);
+        // TODO Auto-generated constructor stub
+        initViews();
+    }
+
+    void initViews() {
+        inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.clearable_edit_text, this, true);
+        edit_text = (EditText) findViewById(R.id.clearable_edit);
+        edit_text.setSingleLine();
+        edit_text.setImeOptions(EditorInfo.IME_ACTION_DONE);
+        btn_clear = (Button) findViewById(R.id.clearable_button_clear);
+        btn_clear.setVisibility(RelativeLayout.INVISIBLE);
+
+        // Dummy listener to fix an sdk issue: https://code.google.com/p/android/issues/detail?id=21775 
+        edit_text.setOnDragListener(new OnDragListener() {
+
+            @Override
+            public boolean onDrag(View v, DragEvent event) {
+                if (event.getAction() == DragEvent.ACTION_DROP)
+                    return true;
+                else
+                    return false;
+            }
+        });
+        clearText();
+        showHideClearButton();
+    }
+
+    void clearText() {
+        btn_clear.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                edit_text.setText("");
+            }
+        });
+    }
+
+    void showHideClearButton() {
+        edit_text.addTextChangedListener(new TextWatcher() {
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                if (s.length() > 0)
+                    btn_clear.setVisibility(RelativeLayout.VISIBLE);
+                else
+                    btn_clear.setVisibility(RelativeLayout.INVISIBLE);
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+        });
+    }
+
+    public Editable getText() {
+        Editable text = edit_text.getText();
+        return text;
+    }
+
+    public void setInputType(int typeClassNumber) {
+        edit_text.setFocusableInTouchMode(true);
+        edit_text.requestFocus();
+        edit_text.setInputType(typeClassNumber);
+
+    }
+
+    public EditText getEdit_text() {
+        return edit_text;
+    }
+
+    public void setError(String string) {
+        edit_text.setError(string);
+        edit_text.requestFocus();
+    }
+
+    public void setTextWatcher(TextWatcher l) {
+        watch = l;
+        edit_text.addTextChangedListener(watch);
+    }
+
+    public void unsetTextWatcher() {
+        edit_text.removeTextChangedListener(watch);
+    }
+
+    public void setOnEditorActionListener(OnEditorActionListener onEditorActionListener) {
+        edit_text.setOnEditorActionListener(onEditorActionListener);
+        
+    }
+}
diff --git a/ring-android/src/cx/ring/views/CredentialsPreference.java b/ring-android/src/cx/ring/views/CredentialsPreference.java
new file mode 100644
index 0000000..24f3d30
--- /dev/null
+++ b/ring-android/src/cx/ring/views/CredentialsPreference.java
@@ -0,0 +1,176 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import java.util.HashMap;
+
+import cx.ring.R;
+
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+import cx.ring.model.account.AccountCredentials;
+import cx.ring.model.account.CredentialsManager;
+
+public class CredentialsPreference extends DialogPreference {
+
+    EditText mUsernameField;
+    PasswordEditText mPasswordField;
+    EditText mRealmField;
+
+    public CredentialsPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.credentials_pref, null);
+
+        mUsernameField = (EditText) view.findViewById(R.id.credentials_username);
+        mPasswordField = (PasswordEditText) view.findViewById(R.id.credentials_password);
+        mRealmField = (EditText) view.findViewById(R.id.credentials_realm);
+
+        if (getExtras().getSerializable(CredentialsManager.CURRENT_CRED) != null) {
+            HashMap<String, String> details = (HashMap<String, String>) getExtras().getSerializable(CredentialsManager.CURRENT_CRED);
+            mUsernameField.setText(details.get(AccountCredentials.CONFIG_ACCOUNT_USERNAME));
+            mPasswordField.getEdit_text().setText(details.get(AccountCredentials.CONFIG_ACCOUNT_PASSWORD));
+            mRealmField.setText(details.get(AccountCredentials.CONFIG_ACCOUNT_REALM));
+        }
+
+        mRealmField.setOnEditorActionListener(new OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                String to = mRealmField.getText().toString();
+                if (to.contentEquals("")) {
+                    mRealmField.setError(getContext().getString(R.string.dial_error_no_number_dialed));
+                }
+                return true;
+            }
+        });
+
+        return view;
+    }
+
+    private boolean isValid() {
+        return mUsernameField.getText().length() > 0 && mPasswordField.getText().length() > 0 && mRealmField.getText().length() > 0;
+    }
+
+    @Override
+    protected void showDialog(Bundle state) {
+        super.showDialog(state);
+
+        final AlertDialog d = (AlertDialog) getDialog();
+
+        // Prevent dismissing the dialog if they are any empty field
+        d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                if (isValid()) {
+                    d.dismiss();
+                    onDialogClosed(true);
+                } else {
+                    Toast t = Toast.makeText(getContext(), "All fields are mandatory!", Toast.LENGTH_LONG);
+                    t.setGravity(Gravity.CENTER, 0, 0);
+                    t.show();
+                }
+            }
+        });
+
+        d.setButton(DialogInterface.BUTTON_NEUTRAL, "Delete", new OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                Bundle toReturn = getExtras();
+                getOnPreferenceChangeListener().onPreferenceChange(CredentialsPreference.this, toReturn);
+            }
+        });
+
+    }
+
+    @Override
+    public void onPrepareDialogBuilder(Builder builder) {
+
+        if (getExtras().getSerializable(CredentialsManager.CURRENT_CRED) != null) {
+            // If the user is editing an entry, he can delete it, otherwise don't show this button
+            builder.setNeutralButton("Delete", new OnClickListener() {
+
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    Bundle toReturn = getExtras();
+                    getOnPreferenceChangeListener().onPreferenceChange(CredentialsPreference.this, toReturn);
+                }
+            });
+        }
+        super.onPrepareDialogBuilder(builder);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            if (getExtras().getSerializable(CredentialsManager.CURRENT_CRED) != null) {
+                Bundle toReturn = getExtras();
+                HashMap<String, String> fields = new HashMap<String, String>();
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_USERNAME, mUsernameField.getText().toString());
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_PASSWORD, mPasswordField.getText().toString());
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_REALM, mRealmField.getText().toString());
+                toReturn.putSerializable(CredentialsManager.NEW_CRED, fields);
+                getOnPreferenceChangeListener().onPreferenceChange(this, toReturn);
+            } else {
+                HashMap<String, String> fields = new HashMap<String, String>();
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_USERNAME, mUsernameField.getText().toString());
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_PASSWORD, mPasswordField.getText().toString());
+                fields.put(AccountCredentials.CONFIG_ACCOUNT_REALM, mRealmField.getText().toString());
+                getOnPreferenceChangeListener().onPreferenceChange(this, new AccountCredentials(fields));
+            }
+
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/HalfCircleImageView.java b/ring-android/src/cx/ring/views/HalfCircleImageView.java
new file mode 100644
index 0000000..e9b0d6a
--- /dev/null
+++ b/ring-android/src/cx/ring/views/HalfCircleImageView.java
@@ -0,0 +1,195 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import cx.ring.R;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class HalfCircleImageView extends ImageView
+{
+    private int borderWidth = 0;
+    private int viewWidth;
+    private int viewHeight;
+    private Bitmap image;
+    private Paint paint;
+    private Paint paintBorder;
+    private Paint backgroundPaint;
+    private RectF viewBounds;
+    
+
+    public HalfCircleImageView(Context context)
+    {
+        super(context);
+        setup();
+    }
+
+    public HalfCircleImageView(Context context, AttributeSet attrs)
+    {
+        super(context, attrs);
+        setup();
+    }
+
+    public HalfCircleImageView(Context context, AttributeSet attrs, int defStyle)
+    {
+        super(context, attrs, defStyle);
+        setup();
+    }
+
+    private void setup()
+    {
+        backgroundPaint = new Paint();
+        backgroundPaint.setColor(getResources().getColor(R.color.sfl_dark_blue));
+        backgroundPaint.setAntiAlias(true);
+        // init paint
+        paint = new Paint();
+        paint.setAntiAlias(true);
+        
+        viewBounds = new RectF();
+
+        paintBorder = new Paint();
+        setBorderColor(Color.WHITE);
+        paintBorder.setAntiAlias(true);
+        this.setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
+        paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
+    }
+
+    public void setBorderWidth(int borderWidth)
+    {
+        this.borderWidth = borderWidth;
+        this.invalidate();
+    }
+
+    public void setBorderColor(int borderColor)
+    {
+        if (paintBorder != null)
+            paintBorder.setColor(borderColor);
+
+        this.invalidate();
+    }
+
+    private void loadBitmap()
+    {
+        BitmapDrawable bitmapDrawable = (BitmapDrawable) this.getDrawable();
+
+        if (bitmapDrawable != null)
+            image = bitmapDrawable.getBitmap();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas)
+    {
+        // load the bitmap
+        loadBitmap();
+        
+        canvas.drawArc(viewBounds, 180, 180, false, backgroundPaint);
+
+        // init shader
+        if (image != null)
+        {
+//            shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvas.getWidth(), canvas.getHeight(), false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+//            paint.setShader(shader);
+//            int circleCenter = viewWidth / 2;
+
+            // circleCenter is the x or y of the view's center
+            // radius is the radius in pixels of the cirle to be drawn
+            // paint contains the shader that will texture the shape
+//            canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, circleCenter + borderWidth - 4.0f, paintBorder);
+            canvas.drawBitmap(image, viewWidth / 2 - image.getWidth() / 2, viewHeight / 3 - image.getHeight() / 2, paint);
+            
+            
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+    {
+        int width = measureWidth(widthMeasureSpec);
+        int height = measureHeight(heightMeasureSpec, widthMeasureSpec);
+
+        viewWidth = width - (borderWidth * 2);
+        viewHeight = height - (borderWidth * 2);
+        
+        viewBounds.set(0, 0, width, height);
+
+        setMeasuredDimension(width, height);
+    }
+
+    private int measureWidth(int measureSpec)
+    {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+
+        if (specMode == MeasureSpec.EXACTLY)
+        {
+            // We were told how big to be
+            result = specSize;
+        }
+        else
+        {
+            // Measure the text
+            result = viewWidth;
+        }
+
+        return result;
+    }
+
+    private int measureHeight(int measureSpecHeight, int measureSpecWidth)
+    {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpecHeight);
+        int specSize = MeasureSpec.getSize(measureSpecHeight);
+
+        if (specMode == MeasureSpec.EXACTLY)
+        {
+            // We were told how big to be
+            result = specSize;
+        }
+        else
+        {
+            // Measure the text (beware: ascent is a negative number)
+            result = viewHeight;
+        }
+
+        return (result);
+    }
+    
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/views/NumberPickerPreference.java b/ring-android/src/cx/ring/views/NumberPickerPreference.java
new file mode 100644
index 0000000..bbf7342
--- /dev/null
+++ b/ring-android/src/cx/ring/views/NumberPickerPreference.java
@@ -0,0 +1,142 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import java.lang.reflect.Field;
+
+import cx.ring.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.NumberPicker;
+
+public class NumberPickerPreference extends DialogPreference {
+    private int mMin, mMax, mDefault;
+
+    private String mMaxExternalKey, mMinExternalKey;
+
+    private NumberPicker mNumberPicker;
+
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // TypedArray dialogType = context.obtainStyledAttributes(attrs,
+        // com.android.internal.R.styleable.DialogPreference, 0, 0);
+        TypedArray numberPickerType = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference, 0, 0);
+
+        mMaxExternalKey = numberPickerType.getString(R.styleable.NumberPickerPreference_maxExternal);
+        mMinExternalKey = numberPickerType.getString(R.styleable.NumberPickerPreference_minExternal);
+
+        mMax = numberPickerType.getInt(R.styleable.NumberPickerPreference_max, 5);
+        mMin = numberPickerType.getInt(R.styleable.NumberPickerPreference_min, 0);
+
+        // mDefault = dialogType.getInt(com.android.internal.R.styleable.Preference_defaultValue, mMin);
+        mDefault = mMin;
+        // dialogType.recycle();
+        numberPickerType.recycle();
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        int max = mMax;
+        int min = mMin;
+
+        // External values
+        if (mMaxExternalKey != null) {
+            max = getSharedPreferences().getInt(mMaxExternalKey, mMax);
+        }
+        if (mMinExternalKey != null) {
+            min = getSharedPreferences().getInt(mMinExternalKey, mMin);
+        }
+
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.number_picker_dialog, null);
+
+        mNumberPicker = (NumberPicker) view.findViewById(R.id.number_picker);
+
+        if (mNumberPicker == null) {
+            throw new RuntimeException("mNumberPicker is null!");
+        }
+
+        // Initialize state
+        mNumberPicker.setWrapSelectorWheel(false);
+        mNumberPicker.setMaxValue(max);
+        mNumberPicker.setMinValue(min);
+        mNumberPicker.setValue(getPersistedInt(mDefault));
+
+        // No keyboard popup
+        disableTextInput(mNumberPicker);
+        // EditText textInput = (EditText) mNumberPicker.findViewById(com.android.internal.R.id.numberpicker_input);
+        // if (textInput != null) {
+        // textInput.setCursorVisible(false);
+        // textInput.setFocusable(false);
+        // textInput.setFocusableInTouchMode(false);
+        // }
+
+        return view;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            persistInt(mNumberPicker.getValue());
+            getOnPreferenceChangeListener().onPreferenceChange(this, String.valueOf(mNumberPicker.getValue()));
+        }
+    }
+
+    /*
+     * reflection of NumberPicker.java verified in 4.1, 4.2
+     */
+    private void disableTextInput(NumberPicker np) {
+        if (np == null)
+            return;
+        Class<?> classType = np.getClass();
+        Field inputTextField;
+        try {
+            inputTextField = classType.getDeclaredField("mInputText");
+            inputTextField.setAccessible(true);
+            EditText textInput = (EditText) inputTextField.get(np);
+            if (textInput != null) {
+                textInput.setCursorVisible(false);
+                textInput.setFocusable(false);
+                textInput.setFocusableInTouchMode(false);
+            }
+        } catch (Exception e) {
+            Log.d("trebuchet", "NumberPickerPreference disableTextInput error", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/views/PagerSlidingTabStrip.java b/ring-android/src/cx/ring/views/PagerSlidingTabStrip.java
new file mode 100644
index 0000000..ff363c5
--- /dev/null
+++ b/ring-android/src/cx/ring/views/PagerSlidingTabStrip.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2013 Andreas Stuetz <andreas.stuetz@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cx.ring.views;
+
+import java.util.Locale;
+
+import cx.ring.R;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class PagerSlidingTabStrip extends HorizontalScrollView {
+
+    public interface IconTabProvider {
+        public int getPageIconResId(int position);
+    }
+
+    // @formatter:off
+    private static final int[] ATTRS = new int[] { android.R.attr.textSize, android.R.attr.textColor };
+    // @formatter:on
+
+    private LinearLayout.LayoutParams defaultTabLayoutParams;
+    private LinearLayout.LayoutParams expandedTabLayoutParams;
+
+    private final PageListener pageListener = new PageListener();
+    public OnPageChangeListener delegatePageListener;
+
+    private LinearLayout tabsContainer;
+    private ViewPager pager;
+
+    private int tabCount;
+
+    private int currentPosition = 0;
+    private float currentPositionOffset = 0f;
+
+    private Paint rectPaint;
+    private Paint dividerPaint;
+
+    private boolean checkedTabWidths = false;
+
+    private int indicatorColor = 0xFF666666;
+    private int underlineColor = 0x1A000000;
+    private int dividerColor = 0x1A000000;
+
+    private boolean shouldExpand = false;
+    private boolean textAllCaps = true;
+
+    private int scrollOffset = 52;
+    private int indicatorHeight = 8;
+    private int underlineHeight = 2;
+    private int dividerPadding = 12;
+    private int tabPadding = 24;
+    private int dividerWidth = 1;
+
+    private int tabTextSize = 12;
+    private int tabTextColor = 0xFF666666;
+    private Typeface tabTypeface = null;
+    private int tabTypefaceStyle = Typeface.BOLD;
+
+    private int lastScrollX = 0;
+
+    private int tabBackgroundResId = R.drawable.background_tabs;
+
+    private Locale locale;
+
+    public PagerSlidingTabStrip(Context context) {
+        this(context, null);
+    }
+
+    public PagerSlidingTabStrip(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setFillViewport(true);
+        setWillNotDraw(false);
+
+        tabsContainer = new LinearLayout(context);
+        tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
+        tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        addView(tabsContainer);
+
+        DisplayMetrics dm = getResources().getDisplayMetrics();
+
+        scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);
+        indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);
+        underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);
+        dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);
+        tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);
+        dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);
+        tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);
+
+        // get system attrs (android:textSize and android:textColor)
+
+        TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+
+        tabTextSize = a.getDimensionPixelSize(0, tabTextSize);
+        tabTextColor = a.getColor(1, tabTextColor);
+
+        a.recycle();
+
+        // get custom attrs
+
+        a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);
+
+        indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_indicatorColor, indicatorColor);
+        underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_underlineColor, underlineColor);
+        dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_dividerColor, dividerColor);
+        indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_indicatorHeight, indicatorHeight);
+        underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_underlineHeight, underlineHeight);
+        dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_dividerPadding, dividerPadding);
+        tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_tabPaddingLeftRight, tabPadding);
+        tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_tabBackground, tabBackgroundResId);
+        shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_shouldExpand, shouldExpand);
+        scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_scrollOffset, scrollOffset);
+        textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_textAllCaps, textAllCaps);
+
+        a.recycle();
+
+        rectPaint = new Paint();
+        rectPaint.setAntiAlias(true);
+        rectPaint.setStyle(Style.FILL);
+
+        dividerPaint = new Paint();
+        dividerPaint.setAntiAlias(true);
+        dividerPaint.setStrokeWidth(dividerWidth);
+
+        defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+        expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
+
+        if (locale == null) {
+            locale = getResources().getConfiguration().locale;
+        }
+    }
+
+    public void setViewPager(ViewPager pager) {
+        this.pager = pager;
+
+        if (pager.getAdapter() == null) {
+            throw new IllegalStateException("ViewPager does not have adapter instance.");
+        }
+
+        pager.setOnPageChangeListener(pageListener);
+
+        notifyDataSetChanged();
+    }
+
+    public void setOnPageChangeListener(OnPageChangeListener listener) {
+        this.delegatePageListener = listener;
+    }
+
+    public void notifyDataSetChanged() {
+
+        tabsContainer.removeAllViews();
+
+        tabCount = pager.getAdapter().getCount();
+
+        for (int i = 0; i < tabCount; i++) {
+
+            if (pager.getAdapter() instanceof IconTabProvider) {
+                addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
+            } else {
+                addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
+            }
+
+        }
+
+        updateTabStyles();
+
+        checkedTabWidths = false;
+
+        getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+
+            @SuppressWarnings("deprecation")
+            @SuppressLint("NewApi")
+            @Override
+            public void onGlobalLayout() {
+
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
+                } else {
+                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                }
+
+                currentPosition = pager.getCurrentItem();
+                scrollToChild(currentPosition, 0);
+            }
+        });
+
+    }
+
+    private void addTextTab(final int position, String title) {
+
+        TextView tab = new TextView(getContext());
+        tab.setText(title);
+        tab.setFocusable(true);
+        tab.setGravity(Gravity.CENTER);
+        tab.setSingleLine();
+
+        tab.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                pager.setCurrentItem(position);
+            }
+        });
+
+        tabsContainer.addView(tab);
+
+    }
+
+    private void addIconTab(final int position, int resId) {
+
+        ImageButton tab = new ImageButton(getContext());
+        tab.setFocusable(true);
+        tab.setImageResource(resId);
+
+        tab.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                pager.setCurrentItem(position);
+            }
+        });
+
+        tabsContainer.addView(tab);
+
+    }
+
+    private void updateTabStyles() {
+
+        for (int i = 0; i < tabCount; i++) {
+
+            View v = tabsContainer.getChildAt(i);
+
+            v.setLayoutParams(defaultTabLayoutParams);
+            v.setBackgroundResource(tabBackgroundResId);
+            if (shouldExpand) {
+                v.setPadding(0, 0, 0, 0);
+            } else {
+                v.setPadding(tabPadding, 0, tabPadding, 0);
+            }
+
+            if (v instanceof TextView) {
+
+                TextView tab = (TextView) v;
+                tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
+                tab.setTypeface(tabTypeface, tabTypefaceStyle);
+                tab.setTextColor(tabTextColor);
+
+                // setAllCaps() is only available from API 14, so the upper case is made manually if we are on a
+                // pre-ICS-build
+                if (textAllCaps) {
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                        tab.setAllCaps(true);
+                    } else {
+                        tab.setText(tab.getText().toString().toUpperCase(locale));
+                    }
+                }
+            }
+        }
+
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!shouldExpand || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
+            return;
+        }
+
+        int myWidth = getMeasuredWidth();
+        int childWidth = 0;
+        for (int i = 0; i < tabCount; i++) {
+            childWidth += tabsContainer.getChildAt(i).getMeasuredWidth();
+        }
+
+        if (!checkedTabWidths && childWidth > 0 && myWidth > 0) {
+
+            if (childWidth <= myWidth) {
+                for (int i = 0; i < tabCount; i++) {
+                    tabsContainer.getChildAt(i).setLayoutParams(expandedTabLayoutParams);
+                }
+            }
+
+            checkedTabWidths = true;
+        }
+    }
+
+    private void scrollToChild(int position, int offset) {
+
+        if (tabCount == 0) {
+            return;
+        }
+
+        int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset;
+
+        if (position > 0 || offset > 0) {
+            newScrollX -= scrollOffset;
+        }
+
+        if (newScrollX != lastScrollX) {
+            lastScrollX = newScrollX;
+            scrollTo(newScrollX, 0);
+        }
+
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (isInEditMode() || tabCount == 0) {
+            return;
+        }
+
+        final int height = getHeight();
+
+        // draw indicator line
+
+        rectPaint.setColor(indicatorColor);
+
+        // default: line below current tab
+        View currentTab = tabsContainer.getChildAt(currentPosition);
+        float lineLeft = currentTab.getLeft();
+        float lineRight = currentTab.getRight();
+
+        // if there is an offset, start interpolating left and right coordinates between current and next tab
+        if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
+
+            View nextTab = tabsContainer.getChildAt(currentPosition + 1);
+            final float nextTabLeft = nextTab.getLeft();
+            final float nextTabRight = nextTab.getRight();
+
+            lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
+            lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
+        }
+
+        canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
+
+        // draw underline
+
+        rectPaint.setColor(underlineColor);
+        canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
+
+        // draw divider
+
+        dividerPaint.setColor(dividerColor);
+        for (int i = 0; i < tabCount - 1; i++) {
+            View tab = tabsContainer.getChildAt(i);
+            canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);
+        }
+    }
+
+    private class PageListener implements OnPageChangeListener {
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+
+            currentPosition = position;
+            currentPositionOffset = positionOffset;
+
+            scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
+
+            invalidate();
+
+            if (delegatePageListener != null) {
+                delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
+            }
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+            if (state == ViewPager.SCROLL_STATE_IDLE) {
+                scrollToChild(pager.getCurrentItem(), 0);
+            }
+
+            if (delegatePageListener != null) {
+                delegatePageListener.onPageScrollStateChanged(state);
+            }
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            if (delegatePageListener != null) {
+                delegatePageListener.onPageSelected(position);
+            }
+        }
+
+    }
+
+    public void setIndicatorColor(int indicatorColor) {
+        this.indicatorColor = indicatorColor;
+        invalidate();
+    }
+
+    public void setIndicatorColorResource(int resId) {
+        this.indicatorColor = getResources().getColor(resId);
+        invalidate();
+    }
+
+    public int getIndicatorColor() {
+        return this.indicatorColor;
+    }
+
+    public void setIndicatorHeight(int indicatorLineHeightPx) {
+        this.indicatorHeight = indicatorLineHeightPx;
+        invalidate();
+    }
+
+    public int getIndicatorHeight() {
+        return indicatorHeight;
+    }
+
+    public void setUnderlineColor(int underlineColor) {
+        this.underlineColor = underlineColor;
+        invalidate();
+    }
+
+    public void setUnderlineColorResource(int resId) {
+        this.underlineColor = getResources().getColor(resId);
+        invalidate();
+    }
+
+    public int getUnderlineColor() {
+        return underlineColor;
+    }
+
+    public void setDividerColor(int dividerColor) {
+        this.dividerColor = dividerColor;
+        invalidate();
+    }
+
+    public void setDividerColorResource(int resId) {
+        this.dividerColor = getResources().getColor(resId);
+        invalidate();
+    }
+
+    public int getDividerColor() {
+        return dividerColor;
+    }
+
+    public void setUnderlineHeight(int underlineHeightPx) {
+        this.underlineHeight = underlineHeightPx;
+        invalidate();
+    }
+
+    public int getUnderlineHeight() {
+        return underlineHeight;
+    }
+
+    public void setDividerPadding(int dividerPaddingPx) {
+        this.dividerPadding = dividerPaddingPx;
+        invalidate();
+    }
+
+    public int getDividerPadding() {
+        return dividerPadding;
+    }
+
+    public void setScrollOffset(int scrollOffsetPx) {
+        this.scrollOffset = scrollOffsetPx;
+        invalidate();
+    }
+
+    public int getScrollOffset() {
+        return scrollOffset;
+    }
+
+    public void setShouldExpand(boolean shouldExpand) {
+        this.shouldExpand = shouldExpand;
+        requestLayout();
+    }
+
+    public boolean getShouldExpand() {
+        return shouldExpand;
+    }
+
+    public boolean isTextAllCaps() {
+        return textAllCaps;
+    }
+
+    public void setAllCaps(boolean textAllCaps) {
+        this.textAllCaps = textAllCaps;
+    }
+
+    public void setTextSize(int textSizePx) {
+        this.tabTextSize = textSizePx;
+        updateTabStyles();
+    }
+
+    public int getTextSize() {
+        return tabTextSize;
+    }
+
+    public void setTextColor(int textColor) {
+        this.tabTextColor = textColor;
+        updateTabStyles();
+    }
+
+    public void setTextColorResource(int resId) {
+        this.tabTextColor = getResources().getColor(resId);
+        updateTabStyles();
+    }
+
+    public int getTextColor() {
+        return tabTextColor;
+    }
+
+    public void setTypeface(Typeface typeface, int style) {
+        this.tabTypeface = typeface;
+        this.tabTypefaceStyle = style;
+        updateTabStyles();
+    }
+
+    public void setTabBackground(int resId) {
+        this.tabBackgroundResId = resId;
+    }
+
+    public int getTabBackground() {
+        return tabBackgroundResId;
+    }
+
+    public void setTabPaddingLeftRight(int paddingPx) {
+        this.tabPadding = paddingPx;
+        updateTabStyles();
+    }
+
+    public int getTabPaddingLeftRight() {
+        return tabPadding;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState savedState = (SavedState) state;
+        super.onRestoreInstanceState(savedState.getSuperState());
+        currentPosition = savedState.currentPosition;
+        requestLayout();
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState savedState = new SavedState(superState);
+        savedState.currentPosition = currentPosition;
+        return savedState;
+    }
+
+    static class SavedState extends BaseSavedState {
+        int currentPosition;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            currentPosition = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(currentPosition);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/PasswordEditText.java b/ring-android/src/cx/ring/views/PasswordEditText.java
new file mode 100644
index 0000000..a6c62f5
--- /dev/null
+++ b/ring-android/src/cx/ring/views/PasswordEditText.java
@@ -0,0 +1,115 @@
+package cx.ring.views;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.text.method.HideReturnsTransformationMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import cx.ring.R;
+
+/**
+ * Created by lisional on 06/04/14.
+ */
+public class PasswordEditText extends RelativeLayout {
+    LayoutInflater inflater = null;
+    EditText edit_text;
+    Button btn_clear;
+
+    public PasswordEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initViews();
+    }
+
+    public PasswordEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initViews();
+    }
+
+    public PasswordEditText(Context context) {
+        super(context);
+        // TODO Auto-generated constructor stub
+        initViews();
+    }
+
+    void initViews() {
+        inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.password_edittext, this, true);
+        edit_text = (EditText) findViewById(R.id.password_edittext);
+        edit_text.setSingleLine();
+        edit_text.setImeOptions(EditorInfo.IME_ACTION_DONE);
+        btn_clear = (Button) findViewById(R.id.password_visibility);
+        btn_clear.setVisibility(RelativeLayout.INVISIBLE);
+        revealText();
+        edit_text.setTransformationMethod(PasswordTransformationMethod.getInstance());
+        showHideClearButton();
+    }
+
+    void revealText() {
+        btn_clear.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                switch(event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        edit_text.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
+                        edit_text.setSelection(edit_text.getText().length());
+                        return true; // if you want to handle the touch event
+                    case MotionEvent.ACTION_UP:
+                        // RELEASED
+                        edit_text.setTransformationMethod(PasswordTransformationMethod.getInstance());
+                        edit_text.setSelection(edit_text.getText().length());
+                        return true; // if you want to handle the touch event
+                }
+                return false;
+            }
+        });
+    }
+
+    void showHideClearButton() {
+        edit_text.addTextChangedListener(new TextWatcher() {
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                if (s.length() > 0)
+                    btn_clear.setVisibility(RelativeLayout.VISIBLE);
+                else
+                    btn_clear.setVisibility(RelativeLayout.INVISIBLE);
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+        });
+    }
+
+    public Editable getText() {
+        Editable text = edit_text.getText();
+        return text;
+    }
+
+    public void setInputType(int typeClassNumber) {
+        edit_text.setFocusableInTouchMode(true);
+        edit_text.requestFocus();
+        edit_text.setInputType(typeClassNumber);
+    }
+
+    public EditText getEdit_text() {
+        return edit_text;
+    }
+
+    public void setError(String string) {
+        edit_text.setError(string);
+        edit_text.requestFocus();
+    }
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/views/PasswordPreference.java b/ring-android/src/cx/ring/views/PasswordPreference.java
new file mode 100644
index 0000000..1727233
--- /dev/null
+++ b/ring-android/src/cx/ring/views/PasswordPreference.java
@@ -0,0 +1,152 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import cx.ring.R;
+import cx.ring.utils.Compatibility;
+
+import android.content.Context;
+import android.view.View.OnClickListener;
+import android.preference.EditTextPreference;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+public class PasswordPreference extends EditTextPreference implements OnClickListener, TextWatcher {
+
+    private static final String THIS_FILE = "PasswordPreference";
+    private CheckBox showPwdCheckbox;
+
+    private boolean canShowPassword = false;
+
+    public PasswordPreference(Context context) {
+        this(context, null);
+    }
+
+    public PasswordPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
+        super.onAddEditTextToDialogView(dialogView, editText);
+        editText.addTextChangedListener(this);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        try {
+            if (showPwdCheckbox == null) {
+                showPwdCheckbox = new CheckBox(getContext());
+                showPwdCheckbox.setText(R.string.show_password);
+                showPwdCheckbox.setOnClickListener(this);
+            }
+
+            canShowPassword = TextUtils.isEmpty(getText());
+            getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+            updateCanShowPassword();
+            ViewParent oldParent = showPwdCheckbox.getParent();
+            if (oldParent != view) {
+                if (oldParent != null) {
+                    ((ViewGroup) oldParent).removeView(showPwdCheckbox);
+                }
+            }
+
+            ViewGroup container = (ViewGroup) view;
+            if (Compatibility.isCompatible(8)) {
+                container = (ViewGroup) container.getChildAt(0);
+            }
+            if (container != null) {
+                container.addView(showPwdCheckbox, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            }
+        } catch (Exception e) {
+            // Just do nothing in case weird ROM in use
+            Log.w(THIS_FILE, "Unsupported device for enhanced password", e);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (!canShowPassword) {
+            // Even if not shown, be very very sure we never come here
+            return;
+        }
+        getEditText().setInputType(
+                InputType.TYPE_CLASS_TEXT
+                        | (((CheckBox) view).isChecked() ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD));
+    }
+
+    @Override
+    public void setText(String text) {
+        super.setText(text);
+        setCanShowPassword(TextUtils.isEmpty(text));
+    }
+
+    private void updateCanShowPassword() {
+        if (showPwdCheckbox != null) {
+            showPwdCheckbox.setVisibility(canShowPassword ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private void setCanShowPassword(boolean canShow) {
+        canShowPassword = canShow;
+        updateCanShowPassword();
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        if (s.length() == 0) {
+            setCanShowPassword(true);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        // Nothing to do
+
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        // Nothing to do
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/src/cx/ring/views/QuadNumberPickerPreference.java b/ring-android/src/cx/ring/views/QuadNumberPickerPreference.java
new file mode 100644
index 0000000..49bf9d3
--- /dev/null
+++ b/ring-android/src/cx/ring/views/QuadNumberPickerPreference.java
@@ -0,0 +1,216 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+/*
+ * Copyright (C) 2011 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.NumberPicker;
+import android.widget.TextView;
+import cx.ring.R;
+
+import java.lang.reflect.Field;
+
+/*
+ * @author Danesh
+ * @author nebkat
+ */
+
+public class QuadNumberPickerPreference extends DialogPreference {
+    private int mMin1, mMax1, mDefault1;
+    private int mMin2, mMax2, mDefault2;
+    private int mMin3, mMax3, mDefault3;
+    private int mMin4, mMax4, mDefault4;
+
+    private String mPickerTitle1;
+    private String mPickerTitle2;
+    private String mPickerTitle3;
+    private String mPickerTitle4;
+
+    private NumberPicker mNumberPicker1;
+    private NumberPicker mNumberPicker2;
+    private NumberPicker mNumberPicker3;
+    private NumberPicker mNumberPicker4;
+
+    public QuadNumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // TypedArray dialogType = context.obtainStyledAttributes(attrs,
+        // com.android.internal.R.styleable.DialogPreference, 0, 0);
+        TypedArray doubleNumberPickerType = context.obtainStyledAttributes(attrs, R.styleable.QuadNumberPickerPreference, 0, 0);
+
+        mPickerTitle1 = doubleNumberPickerType.getString(R.styleable.QuadNumberPickerPreference_pickerTitle1);
+        mPickerTitle2 = doubleNumberPickerType.getString(R.styleable.QuadNumberPickerPreference_pickerTitle2);
+        mPickerTitle3 = doubleNumberPickerType.getString(R.styleable.QuadNumberPickerPreference_pickerTitle3);
+        mPickerTitle4 = doubleNumberPickerType.getString(R.styleable.QuadNumberPickerPreference_pickerTitle4);
+
+
+        mMax1 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_max1, 5);
+        mMin1 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_min1, 0);
+        mMax2 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_max2, 5);
+        mMin2 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_min2, 0);
+        mMax3 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_max3, 5);
+        mMin3 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_min3, 0);
+        mMax4 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_max4, 5);
+        mMin4 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_min4, 0);
+
+
+        mDefault1 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_defaultValue1, mMin1);
+        mDefault2 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_defaultValue2, mMin2);
+        mDefault3 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_defaultValue3, mMin3);
+        mDefault4 = doubleNumberPickerType.getInt(R.styleable.QuadNumberPickerPreference_defaultValue4, mMin4);
+
+        // dialogType.recycle();
+        doubleNumberPickerType.recycle();
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.quad_number_picker_dialog, null);
+
+        mNumberPicker1 = (NumberPicker) view.findViewById(R.id.number_picker_1);
+        mNumberPicker2 = (NumberPicker) view.findViewById(R.id.number_picker_2);
+        mNumberPicker3 = (NumberPicker) view.findViewById(R.id.number_picker_3);
+        mNumberPicker4 = (NumberPicker) view.findViewById(R.id.number_picker_4);
+
+        if (mNumberPicker1 == null || mNumberPicker2 == null || mNumberPicker3 == null || mNumberPicker4 == null) {
+            throw new RuntimeException("mNumberPicker1 or mNumberPicker2 is null!");
+        }
+
+        // Initialize state
+        mNumberPicker1.setWrapSelectorWheel(false);
+        mNumberPicker1.setMaxValue(mMax1);
+        mNumberPicker1.setMinValue(mMin1);
+        mNumberPicker1.setValue(getPersistedValue(mDefault1));
+        mNumberPicker2.setWrapSelectorWheel(false);
+        mNumberPicker2.setMaxValue(mMax2);
+        mNumberPicker2.setMinValue(mMin2);
+        mNumberPicker1.setValue(getPersistedValue(mDefault2));
+        mNumberPicker3.setWrapSelectorWheel(false);
+        mNumberPicker3.setMaxValue(mMax3);
+        mNumberPicker3.setMinValue(mMin3);
+        mNumberPicker1.setValue(getPersistedValue(mDefault3));
+        mNumberPicker4.setWrapSelectorWheel(false);
+        mNumberPicker4.setMaxValue(mMax4);
+        mNumberPicker4.setMinValue(mMin4);
+        mNumberPicker1.setValue(getPersistedValue(mDefault4));
+
+        // Titles
+        TextView pickerTitle1 = (TextView) view.findViewById(R.id.picker_title_1);
+        TextView pickerTitle2 = (TextView) view.findViewById(R.id.picker_title_2);
+        TextView pickerTitle3 = (TextView) view.findViewById(R.id.picker_title_3);
+        TextView pickerTitle4 = (TextView) view.findViewById(R.id.picker_title_4);
+
+        if (pickerTitle1 != null && pickerTitle2 != null) {
+            pickerTitle1.setText(mPickerTitle1);
+            pickerTitle2.setText(mPickerTitle2);
+            pickerTitle3.setText(mPickerTitle3);
+            pickerTitle4.setText(mPickerTitle4);
+        }
+
+        // No keyboard popup
+        disableTextInput(mNumberPicker1);
+        disableTextInput(mNumberPicker2);
+        disableTextInput(mNumberPicker3);
+        disableTextInput(mNumberPicker4);
+        return view;
+    }
+
+    private int getPersistedValue(int value) {
+        String[] values = getPersistedString(mDefault1 + "|" + mDefault2 + "|" + mDefault3 + "|" + mDefault4).split("\\|");
+        if (value == 1) {
+            try {
+                return Integer.parseInt(values[0]);
+            } catch (NumberFormatException e) {
+                return mDefault1;
+            }
+        } else {
+            try {
+                return Integer.parseInt(values[1]);
+            } catch (NumberFormatException e) {
+                return mDefault2;
+            }
+        }
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            persistString(mNumberPicker1.getValue() + "|" + mNumberPicker2.getValue() + "|" + mNumberPicker3.getValue() + "|" + mNumberPicker4.getValue());
+            getOnPreferenceChangeListener().onPreferenceChange(this, mNumberPicker1.getValue() + "" + mNumberPicker2.getValue() + "" + mNumberPicker3.getValue() + "" + mNumberPicker4.getValue());
+        }
+    }
+
+    /*
+     * reflection of NumberPicker.java verified in 4.1, 4.2
+     */
+    private void disableTextInput(NumberPicker np) {
+        if (np == null)
+            return;
+        Class<?> classType = np.getClass();
+        Field inputTextField;
+        try {
+            inputTextField = classType.getDeclaredField("mInputText");
+            inputTextField.setAccessible(true);
+            EditText textInput = (EditText) inputTextField.get(np);
+            if (textInput != null) {
+                textInput.setCursorVisible(false);
+                textInput.setFocusable(false);
+                textInput.setFocusableInTouchMode(false);
+            }
+        } catch (Exception e) {
+            Log.d("QuadNumberPicker", "disableTextInput error", e);
+        }
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/SlidingUpPanelLayout.java b/ring-android/src/cx/ring/views/SlidingUpPanelLayout.java
new file mode 100644
index 0000000..d25b92e
--- /dev/null
+++ b/ring-android/src/cx/ring/views/SlidingUpPanelLayout.java
@@ -0,0 +1,1120 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+public class SlidingUpPanelLayout extends ViewGroup {
+
+    private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
+
+    /**
+     * Default peeking out panel height
+     */
+    private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
+
+    /**
+     * Default height of the shadow above the peeking out panel
+     */
+    private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
+
+    /**
+     * If no fade color is given by default it will fade to 80% gray.
+     */
+    private static final int DEFAULT_FADE_COLOR = 0x99000000;
+
+    /**
+     * Minimum velocity that will be detected as a fling
+     */
+    private static final int MIN_FLING_VELOCITY = 400; // dips per second
+
+    /**
+     * The fade color used for the panel covered by the slider. 0 = no fading.
+     */
+    private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
+
+    /**
+     * The paint used to dim the main layout when sliding
+     */
+    private final Paint mCoveredFadePaint = new Paint();
+
+    /**
+     * Drawable used to draw the shadow between panes.
+     */
+    private Drawable mShadowDrawable;
+
+    /**
+     * The size of the overhang in pixels.
+     */
+    private int mPanelHeight;
+
+    /**
+     * The size of the shadow in pixels.
+     */
+    private final int mShadowHeight;
+
+    /**
+     * True if a panel can slide with the current measurements
+     */
+    private boolean mCanSlide;
+
+    /**
+     * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be used for dragging.
+     */
+    private View mDragView;
+
+    /**
+     * The child view that can slide, if any.
+     */
+    private View mSlideableView;
+
+    /**
+     * How far the panel is offset from its expanded position. range [0, 1] where 0 = expanded, 1 = collapsed.
+     */
+    private float mSlideOffset;
+
+    /**
+     * How far in pixels the slideable panel may move.
+     */
+    private int mSlideRange;
+
+    /**
+     * A panel view is locked into internal scrolling or another condition that is preventing a drag.
+     */
+    private boolean mIsUnableToDrag;
+
+    /**
+     * Flag indicating that sliding feature is enabled\disabled
+     */
+    private boolean mIsSlidingEnabled;
+
+    /**
+     * Flag indicating if a drag view can have its own touch events. If set to true, a drag view can scroll horizontally and have its own click
+     * listener.
+     * 
+     * Default is set to false.
+     */
+    private boolean mIsUsingDragViewTouchEvents;
+
+    /**
+     * Threshold to tell if there was a scroll touch event.
+     */
+    private int mScrollTouchSlop;
+
+    private float mInitialMotionX;
+    private float mInitialMotionY;
+    private boolean mDragViewHit;
+    private float mAnchorPoint = 0.f;
+
+    private PanelSlideListener mPanelSlideListener;
+
+    private final ViewDragHelper mDragHelper;
+
+    /**
+     * Stores whether or not the pane was expanded the last time it was slideable. If expand/collapse operations are invoked this state is modified.
+     * Used by instance state save/restore.
+     */
+    private boolean mPreservedExpandedState;
+    private boolean mFirstLayout = true;
+
+    private final Rect mTmpRect = new Rect();
+
+    /**
+     * Listener for monitoring events about sliding panes.
+     */
+    public interface PanelSlideListener {
+        /**
+         * Called when a sliding pane's position changes.
+         * 
+         * @param panel
+         *            The child view that was moved
+         * @param slideOffset
+         *            The new offset of this sliding pane within its range, from 0-1
+         */
+        public void onPanelSlide(View panel, float slideOffset);
+
+        /**
+         * Called when a sliding pane becomes slid completely collapsed. The pane may or may not be interactive at this point depending on if it's
+         * shown or hidden
+         * 
+         * @param panel
+         *            The child view that was slid to an collapsed position, revealing other panes
+         */
+        public void onPanelCollapsed(View panel);
+
+        /**
+         * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed to be interactive. It may now obscure other views
+         * in the layout.
+         * 
+         * @param panel
+         *            The child view that was slid to a expanded position
+         */
+        public void onPanelExpanded(View panel);
+
+        public void onPanelAnchored(View panel);
+    }
+
+    /**
+     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset of the listener methods you can extend this instead of
+     * implement the full interface.
+     */
+    public static class SimplePanelSlideListener implements PanelSlideListener {
+        @Override
+        public void onPanelSlide(View panel, float slideOffset) {
+        }
+
+        @Override
+        public void onPanelCollapsed(View panel) {
+        }
+
+        @Override
+        public void onPanelExpanded(View panel) {
+        }
+
+        @Override
+        public void onPanelAnchored(View panel) {
+        }
+    }
+
+    public SlidingUpPanelLayout(Context context) {
+        this(context, null);
+    }
+
+    public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final float density = context.getResources().getDisplayMetrics().density;
+        mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
+        mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
+
+        setWillNotDraw(false);
+
+        mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
+        mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
+
+        mCanSlide = true;
+        mIsSlidingEnabled = true;
+
+        setCoveredFadeColor(DEFAULT_FADE_COLOR);
+
+        ViewConfiguration vc = ViewConfiguration.get(context);
+        mScrollTouchSlop = vc.getScaledTouchSlop();
+    }
+
+    /**
+     * Set the color used to fade the pane covered by the sliding pane out when the pane will become fully covered in the expanded state.
+     * 
+     * @param color
+     *            An ARGB-packed color value
+     */
+    public void setCoveredFadeColor(int color) {
+        mCoveredFadeColor = color;
+        invalidate();
+    }
+
+    /**
+     * @return The ARGB-packed color value used to fade the fixed pane
+     */
+    public int getCoveredFadeColor() {
+        return mCoveredFadeColor;
+    }
+
+    /**
+     * Set the collapsed panel height in pixels
+     * 
+     * @param val
+     *            A height in pixels
+     */
+    public void setPanelHeight(int val) {
+        mPanelHeight = val;
+        requestLayout();
+    }
+
+    /**
+     * @return The current collapsed panel height
+     */
+    public int getPanelHeight() {
+        return mPanelHeight;
+    }
+
+    public void setPanelSlideListener(PanelSlideListener listener) {
+        mPanelSlideListener = listener;
+    }
+
+    /**
+     * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
+     * 
+     * @param dragView
+     *            A view that will be used to drag the panel.
+     */
+    public void setDragView(View dragView) {
+        mDragView = dragView;
+    }
+
+    /**
+     * Set an anchor point where the panel can stop during sliding
+     * 
+     * @param anchorPoint
+     *            A value between 0 and 1, determining the position of the anchor point starting from the top of the layout.
+     */
+    public void setAnchorPoint(float anchorPoint) {
+        if (anchorPoint > 0 && anchorPoint < 1)
+            mAnchorPoint = anchorPoint;
+    }
+
+    /**
+     * Set the shadow for the sliding panel
+     * 
+     */
+    public void setShadowDrawable(Drawable drawable) {
+        mShadowDrawable = drawable;
+    }
+
+    void dispatchOnPanelSlide(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
+        }
+    }
+
+    void dispatchOnPanelExpanded(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelExpanded(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void dispatchOnPanelCollapsed(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelCollapsed(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void dispatchOnPanelAnchored(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelAnchored(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void updateObscuredViewVisibility() {
+        if (getChildCount() == 0) {
+            return;
+        }
+        final int leftBound = getPaddingLeft();
+        final int rightBound = getWidth() - getPaddingRight();
+        final int topBound = getPaddingTop();
+        final int bottomBound = getHeight() - getPaddingBottom();
+        final int left;
+        final int right;
+        final int top;
+        final int bottom;
+        if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
+            left = mSlideableView.getLeft();
+            right = mSlideableView.getRight();
+            top = mSlideableView.getTop();
+            bottom = mSlideableView.getBottom();
+        } else {
+            left = right = top = bottom = 0;
+        }
+        View child = getChildAt(0);
+        final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+        final int clampedChildTop = Math.max(topBound, child.getTop());
+        final int clampedChildRight = Math.min(rightBound, child.getRight());
+        final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
+        final int vis;
+        if (clampedChildLeft >= left && clampedChildTop >= top && clampedChildRight <= right && clampedChildBottom <= bottom) {
+            vis = INVISIBLE;
+        } else {
+            vis = VISIBLE;
+        }
+        child.setVisibility(vis);
+    }
+
+    void setAllChildrenVisible() {
+        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == INVISIBLE) {
+                child.setVisibility(VISIBLE);
+            }
+        }
+    }
+
+    private static boolean hasOpaqueBackground(View v) {
+        final Drawable bg = v.getBackground();
+        if (bg != null) {
+            return bg.getOpacity() == PixelFormat.OPAQUE;
+        }
+        return false;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mFirstLayout = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mFirstLayout = true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
+        } else if (heightMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
+        }
+
+        int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
+        int panelHeight = mPanelHeight;
+
+        final int childCount = getChildCount();
+
+        if (childCount > 2) {
+            Log.e(TAG, "onMeasure: More than two child views are not supported.");
+        } else if (getChildAt(1).getVisibility() == GONE) {
+            panelHeight = 0;
+        }
+
+        // We'll find the current one below.
+        mSlideableView = null;
+        mCanSlide = false;
+
+        // First pass. Measure based on child LayoutParams width/height.
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int height = layoutHeight;
+            if (child.getVisibility() == GONE) {
+                lp.dimWhenOffset = false;
+                continue;
+            }
+
+            if (i == 1) {
+                lp.slideable = true;
+                lp.dimWhenOffset = true;
+                mSlideableView = child;
+                mCanSlide = true;
+            } else {
+                height -= panelHeight;
+            }
+
+            int childWidthSpec;
+            if (lp.width == LayoutParams.WRAP_CONTENT) {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+            } else if (lp.width == LayoutParams.MATCH_PARENT) {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+            } else {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+            }
+
+            int childHeightSpec;
+            if (lp.height == LayoutParams.WRAP_CONTENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+            } else if (lp.height == LayoutParams.MATCH_PARENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+            }
+
+            child.measure(childWidthSpec, childHeightSpec);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int paddingLeft = getPaddingLeft();
+        final int paddingTop = getPaddingTop();
+
+        final int childCount = getChildCount();
+        int yStart = paddingTop;
+        int nextYStart = yStart;
+
+        if (mFirstLayout) {
+            mSlideOffset = mCanSlide && mPreservedExpandedState ? 0.f : 1.f;
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int childHeight = child.getMeasuredHeight();
+
+            if (lp.slideable) {
+                mSlideRange = childHeight - mPanelHeight;
+                yStart += (int) (mSlideRange * mSlideOffset);
+            } else {
+                yStart = nextYStart;
+            }
+
+            final int childTop = yStart;
+            final int childBottom = childTop + childHeight;
+            final int childLeft = paddingLeft;
+            final int childRight = childLeft + child.getMeasuredWidth();
+            child.layout(childLeft, childTop, childRight, childBottom);
+
+            nextYStart += child.getHeight();
+        }
+
+        if (mFirstLayout) {
+            updateObscuredViewVisibility();
+        }
+
+        mFirstLayout = false;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        // Recalculate sliding panes and their details
+        if (h != oldh) {
+            mFirstLayout = true;
+        }
+    }
+
+    /**
+     * Set sliding enabled flag
+     * 
+     * @param enabled
+     *            flag value
+     */
+    public void setSlidingEnabled(boolean enabled) {
+        mIsSlidingEnabled = enabled;
+    }
+
+    /**
+     * Set if the drag view can have its own touch events. If set to true, a drag view can scroll horizontally and have its own click listener.
+     * 
+     * Default is set to false.
+     */
+    public void setEnableDragViewTouchEvents(boolean enabled) {
+        mIsUsingDragViewTouchEvents = enabled;
+    }
+
+    private boolean isDragViewHit(int x, int y) {
+        View v = mDragView != null ? mDragView : mSlideableView;
+        if (v == null)
+            return false;
+        int[] viewLocation = new int[2];
+        v.getLocationOnScreen(viewLocation);
+        int[] parentLocation = new int[2];
+        this.getLocationOnScreen(parentLocation);
+        int screenX = parentLocation[0] + x;
+        int screenY = parentLocation[1] + y;
+        return screenX >= viewLocation[0] && screenX < viewLocation[0] + v.getWidth() && screenY >= viewLocation[1]
+                && screenY < viewLocation[1] + v.getHeight();
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (!isInTouchMode() && !mCanSlide) {
+            mPreservedExpandedState = child == mSlideableView;
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (!mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
+            mDragHelper.cancel();
+            return super.onInterceptTouchEvent(ev);
+        }
+
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            mDragHelper.cancel();
+            return false;
+        }
+
+        final float x = ev.getX();
+        final float y = ev.getY();
+        boolean interceptTap = false;
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN: {
+            mIsUnableToDrag = false;
+            mInitialMotionX = x;
+            mInitialMotionY = y;
+            mDragViewHit = isDragViewHit((int) x, (int) y);
+
+            if (mDragViewHit && !mIsUsingDragViewTouchEvents) {
+                interceptTap = true;
+            }
+            break;
+        }
+
+        case MotionEvent.ACTION_MOVE: {
+            final float adx = Math.abs(x - mInitialMotionX);
+            final float ady = Math.abs(y - mInitialMotionY);
+            final int dragSlop = mDragHelper.getTouchSlop();
+
+            // Handle any horizontal scrolling on the drag view.
+            if (mIsUsingDragViewTouchEvents) {
+                if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) {
+                    return super.onInterceptTouchEvent(ev);
+                }
+                // Intercept the touch if the drag view has any vertical scroll.
+                // onTouchEvent will determine if the view should drag vertically.
+                else if (ady > mScrollTouchSlop) {
+                    interceptTap = mDragViewHit;
+                }
+            }
+
+            if (ady > dragSlop && adx > ady) {
+                mDragHelper.cancel();
+                mIsUnableToDrag = true;
+                return false;
+            }
+            break;
+        }
+        }
+
+        final boolean interceptForDrag = mDragViewHit && mDragHelper.shouldInterceptTouchEvent(ev);
+
+        return interceptForDrag || interceptTap;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!mCanSlide || !mIsSlidingEnabled) {
+            return super.onTouchEvent(ev);
+        }
+
+        mDragHelper.processTouchEvent(ev);
+
+        final int action = ev.getAction();
+        boolean wantTouchEvents = true;
+
+        switch (action & MotionEventCompat.ACTION_MASK) {
+        case MotionEvent.ACTION_DOWN: {
+            final float x = ev.getX();
+            final float y = ev.getY();
+            mInitialMotionX = x;
+            mInitialMotionY = y;
+            break;
+        }
+
+        case MotionEvent.ACTION_UP: {
+            final float x = ev.getX();
+            final float y = ev.getY();
+            final float dx = x - mInitialMotionX;
+            final float dy = y - mInitialMotionY;
+            final int slop = mDragHelper.getTouchSlop();
+            if (dx * dx + dy * dy < slop * slop && isDragViewHit((int) x, (int) y)) {
+                View v = mDragView != null ? mDragView : mSlideableView;
+                v.playSoundEffect(SoundEffectConstants.CLICK);
+                if (!isExpanded() && !isAnchored()) {
+                    expandPane(mSlideableView, 0, mAnchorPoint);
+                } else {
+                    collapsePane();
+                }
+                break;
+            }
+            break;
+        }
+        }
+
+        return wantTouchEvents;
+    }
+
+    private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) {
+        if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) {
+            mPreservedExpandedState = true;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean collapsePane(View pane, int initialVelocity) {
+        if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
+            mPreservedExpandedState = false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Collapse the sliding pane if it is currently slideable. If first layout has already completed this will animate.
+     * 
+     * @return true if the pane was slideable and is now collapsed/in the process of collapsing
+     */
+    public boolean collapsePane() {
+        return collapsePane(mSlideableView, 0);
+    }
+
+    /**
+     * Expand the sliding pane if it is currently slideable. If first layout has already completed this will animate.
+     * 
+     * @return true if the pane was slideable and is now expanded/in the process of expading
+     */
+    public boolean expandPane() {
+        return expandPane(0);
+    }
+
+    /**
+     * Partially expand the sliding pane up to a specific offset
+     * 
+     * @param mSlideOffset
+     *            Value between 0 and 1, where 0 is completely expanded.
+     * @return true if the pane was slideable and is now expanded/in the process of expading
+     */
+    public boolean expandPane(float mSlideOffset) {
+        if (!isPaneVisible()) {
+            showPane();
+        }
+        return expandPane(mSlideableView, 0, mSlideOffset);
+    }
+
+    /**
+     * Check if the layout is completely expanded.
+     * 
+     * @return true if sliding panels are completely expanded
+     */
+    public boolean isExpanded() {
+        return mFirstLayout && mPreservedExpandedState || !mFirstLayout && mCanSlide && mSlideOffset == 0;
+    }
+
+    /**
+     * Check if the layout is anchored in an intermediate point.
+     * 
+     * @return true if sliding panels are anchored
+     */
+    public boolean isAnchored() {
+        int anchoredTop = (int) (mAnchorPoint * mSlideRange);
+        return !mFirstLayout && mCanSlide && mSlideOffset == (float) anchoredTop / (float) mSlideRange;
+    }
+
+    /**
+     * Check if the content in this layout cannot fully fit side by side and therefore the content pane can be slid back and forth.
+     * 
+     * @return true if content in this layout can be expanded
+     */
+    public boolean isSlideable() {
+        return mCanSlide;
+    }
+
+    public boolean isPaneVisible() {
+        if (getChildCount() < 2) {
+            return false;
+        }
+        View slidingPane = getChildAt(1);
+        return slidingPane.getVisibility() == View.VISIBLE;
+    }
+
+    public void showPane() {
+        if (getChildCount() < 2) {
+            return;
+        }
+        View slidingPane = getChildAt(1);
+        slidingPane.setVisibility(View.VISIBLE);
+        requestLayout();
+    }
+
+    public void hidePane() {
+        if (mSlideableView == null) {
+            return;
+        }
+        mSlideableView.setVisibility(View.GONE);
+        requestLayout();
+    }
+
+    private void onPanelDragged(int newTop) {
+        final int topBound = getPaddingTop();
+        mSlideOffset = (float) (newTop - topBound) / mSlideRange;
+        dispatchOnPanelSlide(mSlideableView);
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        boolean result;
+        final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
+
+        boolean drawScrim = false;
+
+        if (mCanSlide && !lp.slideable && mSlideableView != null) {
+            // Clip against the slider; no sense drawing what will immediately be covered.
+            canvas.getClipBounds(mTmpRect);
+            mTmpRect.bottom = (int) Math.min(mTmpRect.bottom, mSlideableView.getTop() + getResources().getDisplayMetrics().density * 68); // + 60
+                                                                                                                                          // cause of
+                                                                                                                                          // the
+                                                                                                                                          // rounded
+                                                                                                                                          // shape
+                                                                                                                                          // handle
+            canvas.clipRect(mTmpRect);
+            if (mSlideOffset < 1) {
+                drawScrim = true;
+            }
+        }
+
+        result = super.drawChild(canvas, child, drawingTime);
+        canvas.restoreToCount(save);
+
+        if (drawScrim) {
+            final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
+            final int imag = (int) (baseAlpha * (1 - mSlideOffset));
+            final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
+            mCoveredFadePaint.setColor(color);
+            canvas.drawRect(mTmpRect, mCoveredFadePaint);
+        }
+
+        return result;
+    }
+
+    /**
+     * Smoothly animate mDraggingPane to the target X position within its range.
+     * 
+     * @param slideOffset
+     *            position to animate to
+     * @param velocity
+     *            initial velocity in case of fling, or 0.
+     */
+    boolean smoothSlideTo(float slideOffset, int velocity) {
+        if (!mCanSlide) {
+            // Nothing to do.
+            return false;
+        }
+
+        final int topBound = getPaddingTop();
+        int y = (int) (topBound + slideOffset * mSlideRange);
+
+        if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
+            setAllChildrenVisible();
+            ViewCompat.postInvalidateOnAnimation(this);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mDragHelper.continueSettling(true)) {
+            if (!mCanSlide) {
+                mDragHelper.abort();
+                return;
+            }
+
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+
+        if (mSlideableView == null) {
+            // No need to draw a shadow if we don't have one.
+            return;
+        }
+
+        final int right = mSlideableView.getRight();
+        final int top = mSlideableView.getTop() - mShadowHeight;
+        final int bottom = mSlideableView.getTop();
+        final int left = mSlideableView.getLeft();
+
+        if (mShadowDrawable != null) {
+            mShadowDrawable.setBounds(left, top, right, bottom);
+            mShadowDrawable.draw(c);
+        }
+    }
+
+    /**
+     * Tests scrollability within child views of v given a delta of dx.
+     * 
+     * @param v
+     *            View to test for horizontal scrollability
+     * @param checkV
+     *            Whether the view v passed should itself be checked for scrollability (true), or just its children (false).
+     * @param dx
+     *            Delta scrolled in pixels
+     * @param x
+     *            X coordinate of the active touch point
+     * @param y
+     *            Y coordinate of the active touch point
+     * @return true if child views of v can be scrolled by delta of dx.
+     */
+    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+        if (v instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) v;
+            final int scrollX = v.getScrollX();
+            final int scrollY = v.getScrollY();
+            final int count = group.getChildCount();
+            // Count backwards - let topmost views consume scroll distance first.
+            for (int i = count - 1; i >= 0; i--) {
+                final View child = group.getChildAt(i);
+                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop()
+                        && y + scrollY < child.getBottom() && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) {
+                    return true;
+                }
+            }
+        }
+        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : new LayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && super.checkLayoutParams(p);
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+        ss.isExpanded = isSlideable() ? isExpanded() : mPreservedExpandedState;
+
+        return ss;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.isExpanded) {
+            expandPane();
+        } else {
+            collapsePane();
+        }
+        mPreservedExpandedState = ss.isExpanded;
+    }
+
+    private class DragHelperCallback extends ViewDragHelper.Callback {
+
+        @Override
+        public boolean tryCaptureView(View child, int pointerId) {
+            if (mIsUnableToDrag) {
+                return false;
+            }
+
+            return ((LayoutParams) child.getLayoutParams()).slideable;
+        }
+
+        @Override
+        public void onViewDragStateChanged(int state) {
+            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
+                if (mSlideOffset == 0) {
+                    updateObscuredViewVisibility();
+                    dispatchOnPanelExpanded(mSlideableView);
+                    mPreservedExpandedState = true;
+                } else if (isAnchored()) {
+                    updateObscuredViewVisibility();
+                    dispatchOnPanelAnchored(mSlideableView);
+                    mPreservedExpandedState = true;
+                } else {
+                    dispatchOnPanelCollapsed(mSlideableView);
+                    mPreservedExpandedState = false;
+                }
+            }
+        }
+
+        @Override
+        public void onViewCaptured(View capturedChild, int activePointerId) {
+            // Make all child views visible in preparation for sliding things around
+            setAllChildrenVisible();
+        }
+
+        @Override
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+            onPanelDragged(top);
+            invalidate();
+        }
+
+        @Override
+        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+            int top = getPaddingTop();
+
+            if (mAnchorPoint != 0) {
+                int anchoredTop = (int) (mAnchorPoint * mSlideRange);
+                float anchorOffset = (float) anchoredTop / (float) mSlideRange;
+
+                if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f + anchorOffset) / 2)) {
+                    top += mSlideRange;
+                } else if (yvel == 0 && mSlideOffset < (1f + anchorOffset) / 2 && mSlideOffset >= anchorOffset / 2) {
+                    top += mSlideRange * mAnchorPoint;
+                }
+
+            } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
+                top += mSlideRange;
+            }
+
+            mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
+            invalidate();
+        }
+
+        @Override
+        public int getViewVerticalDragRange(View child) {
+            return mSlideRange;
+        }
+
+        @Override
+        public int clampViewPositionVertical(View child, int top, int dy) {
+            final int topBound = getPaddingTop();
+            final int bottomBound = topBound + mSlideRange;
+
+            final int newLeft = Math.min(Math.max(top, topBound), bottomBound);
+
+            return newLeft;
+        }
+
+    }
+
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        private static final int[] ATTRS = new int[] { android.R.attr.layout_weight };
+
+        /**
+         * True if this pane is the slideable pane in the layout.
+         */
+        boolean slideable;
+
+        /**
+         * True if this view should be drawn dimmed when it's been offset from its default position.
+         */
+        boolean dimWhenOffset;
+
+        Paint dimPaint;
+
+        public LayoutParams() {
+            super(MATCH_PARENT, MATCH_PARENT);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
+            a.recycle();
+        }
+
+    }
+
+    static class SavedState extends BaseSavedState {
+        boolean isExpanded;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            isExpanded = in.readInt() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(isExpanded ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+}
diff --git a/ring-android/src/cx/ring/views/SwipeListViewTouchListener.java b/ring-android/src/cx/ring/views/SwipeListViewTouchListener.java
new file mode 100644
index 0000000..8e05466
--- /dev/null
+++ b/ring-android/src/cx/ring/views/SwipeListViewTouchListener.java
@@ -0,0 +1,409 @@
+/*
+ *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+package cx.ring.views;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import cx.ring.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+public class SwipeListViewTouchListener implements View.OnTouchListener {
+    // Cached ViewConfiguration and system-wide constant values
+    private int mSlop;
+    private int mMinFlingVelocity;
+    private int mMaxFlingVelocity;
+    private long mAnimationTime;
+
+    private static final String TAG = SwipeListViewTouchListener.class.getSimpleName();
+
+    // Fixed properties
+    private ListView mListView;
+    private OnSwipeCallback mCallback;
+    private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
+    private boolean dismissLeft = true;
+    private boolean dismissRight = true;
+
+    // Transient properties
+    private List<PendingSwipeData> mPendingSwipes = new ArrayList<PendingSwipeData>();
+    private int mDismissAnimationRefCount = 0;
+    private float mDownX;
+    private float mDownY;
+    private boolean mSwiping;
+    private VelocityTracker mVelocityTracker;
+    private int mDownPosition;
+    private View mDownView, mUnderDownView;
+    private boolean mPaused;
+
+    /**
+     * The callback interface used by {@link SwipeListViewTouchListener} to inform its client about a successful swipe of one or more list item
+     * positions.
+     */
+    public interface OnSwipeCallback {
+        /**
+         * Called when the user has swiped the list item to the left.
+         * 
+         * @param listView
+         *            The originating {@link ListView}.
+         * @param reverseSortedPositions
+         *            An array of positions to dismiss, sorted in descending order for convenience.
+         */
+        void onSwipeLeft(ListView listView, int[] reverseSortedPositions);
+
+        void onSwipeRight(ListView listView, View downView);
+    }
+
+    /**
+     * Constructs a new swipe-to-action touch listener for the given list view.
+     * 
+     * @param listView
+     *            The list view whose items should be dismissable.
+     * @param callback
+     *            The callback to trigger when the user has indicated that she would like to dismiss one or more list items.
+     */
+    public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback) {
+        ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
+        mSlop = vc.getScaledTouchSlop();
+        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
+        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
+        mAnimationTime = listView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
+        mListView = listView;
+        mCallback = callback;
+    }
+
+    /**
+     * Constructs a new swipe-to-action touch listener for the given list view.
+     * 
+     * @param listView
+     *            The list view whose items should be dismissable.
+     * @param callback
+     *            The callback to trigger when the user has indicated that she would like to dismiss one or more list items.
+     * @param dismissLeft
+     *            set if the dismiss animation is up when the user swipe to the left
+     * @param dismissRight
+     *            set if the dismiss animation is up when the user swipe to the right
+     * @see #SwipeListViewTouchListener(ListView, OnSwipeCallback, boolean, boolean)
+     */
+    public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback, boolean dismissLeft, boolean dismissRight) {
+        this(listView, callback);
+        this.dismissLeft = dismissLeft;
+        this.dismissRight = dismissRight;
+    }
+
+    /**
+     * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
+     * 
+     * @param enabled
+     *            Whether or not to watch for gestures.
+     */
+    public void setEnabled(boolean enabled) {
+        mPaused = !enabled;
+    }
+
+    /**
+     * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link ListView} using
+     * {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}. If a scroll listener is already assigned, the caller should
+     * still pass scroll changes through to this listener. This will ensure that this {@link SwipeListViewTouchListener} is paused during list view
+     * scrolling.</p>
+     * 
+     * @see {@link SwipeListViewTouchListener}
+     */
+    public AbsListView.OnScrollListener makeScrollListener() {
+        return new AbsListView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
+                setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+            }
+
+            @Override
+            public void onScroll(AbsListView absListView, int i, int i1, int i2) {
+            }
+        };
+    }
+
+    @Override
+    public boolean onTouch(View item, MotionEvent motionEvent) {
+        if (mViewWidth < 2) {
+            mViewWidth = mListView.getWidth();
+        }
+
+        switch (motionEvent.getActionMasked()) {
+        case MotionEvent.ACTION_DOWN: {
+            if (mPaused) {
+                return false;
+            }
+
+            // TODO: ensure this is a finger, and set a flag
+
+            // Find the child view that was touched (perform a hit test)
+            Rect rect = new Rect();
+            int childCount = mListView.getChildCount();
+            int[] listViewCoords = new int[2];
+            mListView.getLocationOnScreen(listViewCoords);
+            int x = (int) motionEvent.getRawX() - listViewCoords[0];
+            int y = (int) motionEvent.getRawY() - listViewCoords[1];
+            View child;
+            for (int i = 0; i < childCount; i++) {
+                child = mListView.getChildAt(i);
+                child.getHitRect(rect);
+                if (rect.contains(x, y)) {
+                    mDownView = child.findViewById(R.id.contactview);
+                    mUnderDownView = child.findViewById(R.id.contact_underview);
+                    break;
+                }
+            }
+
+            if (mDownView != null) {
+
+                mDownX = motionEvent.getRawX() - mDownView.getTranslationX();
+                mDownY = motionEvent.getRawY();
+                mDownPosition = mListView.getPositionForView(mDownView);
+
+                mVelocityTracker = VelocityTracker.obtain();
+                mVelocityTracker.addMovement(motionEvent);
+            }
+            item.onTouchEvent(motionEvent);
+            return true;
+        }
+
+        case MotionEvent.ACTION_UP: {
+            if (mVelocityTracker == null) {
+                break;
+            }
+
+            float deltaX = motionEvent.getRawX() - mDownX;
+
+            mVelocityTracker.addMovement(motionEvent);
+            mVelocityTracker.computeCurrentVelocity(500); // 1000 by defaut but it was too much
+            float velocityX = Math.abs(mVelocityTracker.getXVelocity());
+            float velocityY = Math.abs(mVelocityTracker.getYVelocity());
+            boolean swipe = false;
+            boolean swipeRight = false;
+
+            if (mDownView.getTranslationX() > mViewWidth / 2) {
+                swipe = true;
+                swipeRight = deltaX > 0;
+            } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
+                swipe = true;
+                swipeRight = mVelocityTracker.getXVelocity() > 0;
+            }
+            if (swipe) {
+                // sufficent swipe value
+                final View downView = mDownView; // mDownView gets null'd before animation ends
+                final int downPosition = mDownPosition;
+                final boolean toTheRight = swipeRight;
+                ++mDismissAnimationRefCount;
+
+                if (toTheRight) {
+                    mDownView.animate().translationX(mViewWidth / 2).alpha(1).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mListView.requestDisallowInterceptTouchEvent(false);
+                            // mCallback.onSwipeRight(mListView, mUnderDownView);
+                            toggleUnderLayerState(true);
+                            // performSwipeAction(downView, downPosition, toTheRight,dismissRight);
+                        }
+                    });
+                } else {
+                    mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime);
+                }
+
+            } else {
+                // cancel
+                mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        toggleUnderLayerState(false);
+                        mUnderDownView = null;
+                    }
+                });
+            }
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+            mDownX = 0;
+            mDownView = null;
+            mDownPosition = ListView.INVALID_POSITION;
+            mSwiping = false;
+            break;
+        }
+
+        case MotionEvent.ACTION_MOVE: {
+            if (mVelocityTracker == null || mPaused) {
+                break;
+            }
+
+            mVelocityTracker.addMovement(motionEvent);
+            mVelocityTracker.computeCurrentVelocity(500);
+            float deltaX = motionEvent.getRawX() - mDownX;
+            float deltaY = motionEvent.getRawY() - mDownY;
+
+            if (Math.abs(deltaX) < Math.abs(deltaY)) {
+                mListView.requestDisallowInterceptTouchEvent(false);
+                return false;
+            }
+
+            if (Math.abs(deltaX) > mSlop) {
+                mSwiping = true;
+                mListView.requestDisallowInterceptTouchEvent(true);
+
+                // Cancel ListView's touch (un-highlighting the item)
+                MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
+                cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
+                mListView.onTouchEvent(cancelEvent);
+                cancelEvent.recycle();
+            }
+            if (deltaX < 0)
+                return true;
+
+            if (mSwiping) {
+                mDownView.setTranslationX(deltaX);
+                // mDownView.setAlpha(Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
+                return true;
+            }
+            break;
+        }
+        }
+        return false;
+    }
+
+    private void toggleUnderLayerState(boolean b) {
+        if (mUnderDownView == null)
+            return;
+        mUnderDownView.findViewById(R.id.quick_edit).setClickable(b);
+        mUnderDownView.findViewById(R.id.quick_discard).setClickable(b);
+        mUnderDownView.findViewById(R.id.quick_starred).setClickable(b);
+    }
+
+    class PendingSwipeData implements Comparable<PendingSwipeData> {
+        public int position;
+        public View view;
+
+        public PendingSwipeData(int position, View view) {
+            this.position = position;
+            this.view = view;
+        }
+
+        @Override
+        public int compareTo(PendingSwipeData other) {
+            // Sort by descending position
+            return other.position - position;
+        }
+    }
+
+    private void performSwipeAction(final View swipeView, final int swipePosition, boolean toTheRight, boolean dismiss) {
+        // Animate the dismissed list item to zero-height and fire the dismiss callback when
+        // all dismissed list item animations have completed. This triggers layout on each animation
+        // frame; in the future we may want to do something smarter and more performant.
+
+        final ViewGroup.LayoutParams lp = swipeView.getLayoutParams();
+        final int originalHeight = swipeView.getHeight();
+        final boolean swipeRight = toTheRight;
+
+        ValueAnimator animator;
+        if (dismiss)
+            animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
+        else
+            animator = ValueAnimator.ofInt(originalHeight, originalHeight - 1).setDuration(mAnimationTime);
+
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                --mDismissAnimationRefCount;
+                if (mDismissAnimationRefCount == 0) {
+                    // No active animations, process all pending dismisses.
+                    // Sort by descending position
+                    Collections.sort(mPendingSwipes);
+
+                    int[] swipePositions = new int[mPendingSwipes.size()];
+                    for (int i = mPendingSwipes.size() - 1; i >= 0; i--) {
+                        swipePositions[i] = mPendingSwipes.get(i).position;
+                    }
+                    // if (swipeRight)
+                    // mCallback.onSwipeRight(mListView, swipePositions);
+                    // else
+                    // mCallback.onSwipeLeft(mListView, swipePositions);
+
+                    ViewGroup.LayoutParams lp;
+                    for (PendingSwipeData pendingDismiss : mPendingSwipes) {
+                        // Reset view presentation
+                        pendingDismiss.view.setAlpha(1f);
+                        pendingDismiss.view.setTranslationX(0);
+                        lp = pendingDismiss.view.getLayoutParams();
+                        lp.height = originalHeight;
+                        pendingDismiss.view.setLayoutParams(lp);
+                    }
+
+                    mPendingSwipes.clear();
+                }
+            }
+        });
+
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                lp.height = (Integer) valueAnimator.getAnimatedValue();
+                swipeView.setLayoutParams(lp);
+            }
+        });
+
+        mPendingSwipes.add(new PendingSwipeData(swipePosition, swipeView));
+        animator.start();
+    }
+
+    public void openItem(View child, int pos, long id) {
+
+        mDownView = child.findViewById(R.id.contactview);
+        mUnderDownView = child.findViewById(R.id.contact_underview);
+        if (mDownView.getTranslationX() > 0)
+            return;
+        mDownView.animate().translationX(mViewWidth / 2).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mListView.requestDisallowInterceptTouchEvent(false);
+                toggleUnderLayerState(true);
+                // performSwipeAction(downView, downPosition, toTheRight,dismissRight);
+            }
+        });
+    }
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/DragSortController.java b/ring-android/src/cx/ring/views/dragsortlv/DragSortController.java
new file mode 100644
index 0000000..616c6ac
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/DragSortController.java
@@ -0,0 +1,508 @@
+/*
+ *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ *
+ *  Part of the library: 
+ *  https://github.com/bauerca/drag-sort-listview
+ *  No longer maintained
+ *
+ *
+ */
+
+
+
+
+package cx.ring.views.dragsortlv;
+
+import android.graphics.Point;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.AdapterView;
+
+/**
+ * Class that starts and stops item drags on a {@link DragSortListView}
+ * based on touch gestures. This class also inherits from
+ * {@link SimpleFloatViewManager}, which provides basic float View
+ * creation.
+ *
+ * An instance of this class is meant to be passed to the methods
+ * {@link DragSortListView#setTouchListener()} and
+ * {@link DragSortListView#setFloatViewManager()} of your
+ * {@link DragSortListView} instance.
+ */
+public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
+
+    /**
+     * Drag init mode enum.
+     */
+    public static final int ON_DOWN = 0;
+    public static final int ON_DRAG = 1;
+    public static final int ON_LONG_PRESS = 2;
+
+    private int mDragInitMode = ON_DOWN;
+
+    private boolean mSortEnabled = true;
+
+    /**
+     * Remove mode enum.
+     */
+    public static final int CLICK_REMOVE = 0;
+    public static final int FLING_REMOVE = 1;
+
+    /**
+     * The current remove mode.
+     */
+    private int mRemoveMode;
+
+    private boolean mRemoveEnabled = false;
+    private boolean mIsRemoving = false;
+
+    private GestureDetector mDetector;
+
+    private GestureDetector mFlingRemoveDetector;
+
+    private int mTouchSlop;
+
+    public static final int MISS = -1;
+
+    private int mHitPos = MISS;
+    private int mFlingHitPos = MISS;
+
+    private int mClickRemoveHitPos = MISS;
+
+    private int[] mTempLoc = new int[2];
+
+    private int mItemX;
+    private int mItemY;
+
+    private int mCurrX;
+    private int mCurrY;
+
+    private boolean mDragging = false;
+
+    private float mFlingSpeed = 500f;
+
+    private int mDragHandleId;
+
+    private int mClickRemoveId;
+
+    private int mFlingHandleId;
+    private boolean mCanDrag;
+
+    private DragSortListView mDslv;
+    private int mPositionX;
+
+    /**
+     * Calls {@link #DragSortController(DragSortListView, int)} with a
+     * 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
+     * and ON_DOWN drag init. By default, sorting is enabled, and
+     * removal is disabled.
+     *
+     * @param dslv The DSLV instance
+     */
+    public DragSortController(DragSortListView dslv) {
+        this(dslv, 0, ON_DOWN, FLING_REMOVE);
+    }
+
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
+        this(dslv, dragHandleId, dragInitMode, removeMode, 0);
+    }
+
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
+        this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
+    }
+
+    /**
+     * By default, sorting is enabled, and removal is disabled.
+     *
+     * @param dslv The DSLV instance
+     * @param dragHandleId The resource id of the View that represents
+     * the drag handle in a list item.
+     */
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
+            int removeMode, int clickRemoveId, int flingHandleId) {
+        super(dslv);
+        mDslv = dslv;
+        mDetector = new GestureDetector(dslv.getContext(), this);
+        mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
+        mFlingRemoveDetector.setIsLongpressEnabled(false);
+        mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
+        mDragHandleId = dragHandleId;
+        mClickRemoveId = clickRemoveId;
+        mFlingHandleId = flingHandleId;
+        setRemoveMode(removeMode);
+        setDragInitMode(dragInitMode);
+    }
+
+
+    public int getDragInitMode() {
+        return mDragInitMode;
+    }
+
+    /**
+     * Set how a drag is initiated. Needs to be one of
+     * {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
+     *
+     * @param mode The drag init mode.
+     */
+    public void setDragInitMode(int mode) {
+        mDragInitMode = mode;
+    }
+
+    /**
+     * Enable/Disable list item sorting. Disabling is useful if only item
+     * removal is desired. Prevents drags in the vertical direction.
+     *
+     * @param enabled Set <code>true</code> to enable list
+     * item sorting.
+     */
+    public void setSortEnabled(boolean enabled) {
+        mSortEnabled = enabled;
+    }
+
+    public boolean isSortEnabled() {
+        return mSortEnabled;
+    }
+
+    /**
+     * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
+     * {@link FLING_LEFT_REMOVE},
+     * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
+     */
+    public void setRemoveMode(int mode) {
+        mRemoveMode = mode;
+    }
+
+    public int getRemoveMode() {
+        return mRemoveMode;
+    }
+
+    /**
+     * Enable/Disable item removal without affecting remove mode.
+     */
+    public void setRemoveEnabled(boolean enabled) {
+        mRemoveEnabled = enabled;
+    }
+
+    public boolean isRemoveEnabled() {
+        return mRemoveEnabled;
+    }
+
+    /**
+     * Set the resource id for the View that represents the drag
+     * handle in a list item.
+     *
+     * @param id An android resource id.
+     */
+    public void setDragHandleId(int id) {
+        mDragHandleId = id;
+    }
+
+    /**
+     * Set the resource id for the View that represents the fling
+     * handle in a list item.
+     *
+     * @param id An android resource id.
+     */
+    public void setFlingHandleId(int id) {
+        mFlingHandleId = id;
+    }
+
+    /**
+     * Set the resource id for the View that represents click
+     * removal button.
+     *
+     * @param id An android resource id.
+     */
+    public void setClickRemoveId(int id) {
+        mClickRemoveId = id;
+    }
+
+    /**
+     * Sets flags to restrict certain motions of the floating View
+     * based on DragSortController settings (such as remove mode).
+     * Starts the drag on the DragSortListView.
+     *
+     * @param position The list item position (includes headers).
+     * @param deltaX Touch x-coord minus left edge of floating View.
+     * @param deltaY Touch y-coord minus top edge of floating View.
+     *
+     * @return True if drag started, false otherwise.
+     */
+    public boolean startDrag(int position, int deltaX, int deltaY) {
+
+        int dragFlags = 0;
+        if (mSortEnabled && !mIsRemoving) {
+            dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
+        }
+        if (mRemoveEnabled && mIsRemoving) {
+            dragFlags |= DragSortListView.DRAG_POS_X;
+            dragFlags |= DragSortListView.DRAG_NEG_X;
+        }
+
+        mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
+                deltaY);
+        return mDragging;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
+            return false;
+        }
+
+        mDetector.onTouchEvent(ev);
+        if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
+            mFlingRemoveDetector.onTouchEvent(ev);
+        }
+
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mCurrX = (int) ev.getX();
+                mCurrY = (int) ev.getY();
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mRemoveEnabled && mIsRemoving) {
+                    int x = mPositionX >= 0 ? mPositionX : -mPositionX;
+                    int removePoint = mDslv.getWidth() / 2;
+                    if (x > removePoint) {
+                        mDslv.stopDragWithVelocity(true, 0);
+                    }
+                }
+            case MotionEvent.ACTION_CANCEL:
+                mIsRemoving = false;
+                mDragging = false;
+                break;
+        }
+
+        return false;
+    }
+
+    /**
+     * Overrides to provide fading when slide removal is enabled.
+     */
+    @Override
+    public void onDragFloatView(View floatView, Point position, Point touch) {
+
+        if (mRemoveEnabled && mIsRemoving) {
+            mPositionX = position.x;
+        }
+    }
+
+    /**
+     * Get the position to start dragging based on the ACTION_DOWN
+     * MotionEvent. This function simply calls
+     * {@link #dragHandleHitPosition(MotionEvent)}. Override
+     * to change drag handle behavior;
+     * this function is called internally when an ACTION_DOWN
+     * event is detected.
+     *
+     * @param ev The ACTION_DOWN MotionEvent.
+     *
+     * @return The list position to drag if a drag-init gesture is
+     * detected; MISS if unsuccessful.
+     */
+    public int startDragPosition(MotionEvent ev) {
+        return dragHandleHitPosition(ev);
+    }
+
+    public int startFlingPosition(MotionEvent ev) {
+        return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
+    }
+
+    /**
+     * Checks for the touch of an item's drag handle (specified by
+     * {@link #setDragHandleId(int)}), and returns that item's position
+     * if a drag handle touch was detected.
+     *
+     * @param ev The ACTION_DOWN MotionEvent.
+
+     * @return The list position of the item whose drag handle was
+     * touched; MISS if unsuccessful.
+     */
+    public int dragHandleHitPosition(MotionEvent ev) {
+        return viewIdHitPosition(ev, mDragHandleId);
+    }
+
+    public int flingHandleHitPosition(MotionEvent ev) {
+        return viewIdHitPosition(ev, mFlingHandleId);
+    }
+
+    public int viewIdHitPosition(MotionEvent ev, int id) {
+        final int x = (int) ev.getX();
+        final int y = (int) ev.getY();
+
+        int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
+
+        final int numHeaders = mDslv.getHeaderViewsCount();
+        final int numFooters = mDslv.getFooterViewsCount();
+        final int count = mDslv.getCount();
+
+        // Log.d("mobeta", "touch down on position " + itemnum);
+        // We're only interested if the touch was on an
+        // item that's not a header or footer.
+        if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
+                && touchPos < (count - numFooters)) {
+            final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
+            final int rawX = (int) ev.getRawX();
+            final int rawY = (int) ev.getRawY();
+
+            View dragBox = id == 0 ? item : (View) item.findViewById(id);
+            if (dragBox != null) {
+                dragBox.getLocationOnScreen(mTempLoc);
+
+                if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
+                        rawX < mTempLoc[0] + dragBox.getWidth() &&
+                        rawY < mTempLoc[1] + dragBox.getHeight()) {
+
+                    mItemX = item.getLeft();
+                    mItemY = item.getTop();
+
+                    return touchPos;
+                }
+            }
+        }
+
+        return MISS;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent ev) {
+        if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+            mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
+        }
+
+        mHitPos = startDragPosition(ev);
+        if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
+            startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
+        }
+
+        mIsRemoving = false;
+        mCanDrag = true;
+        mPositionX = 0;
+        mFlingHitPos = startFlingPosition(ev);
+
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+
+        final int x1 = (int) e1.getX();
+        final int y1 = (int) e1.getY();
+        final int x2 = (int) e2.getX();
+        final int y2 = (int) e2.getY();
+        final int deltaX = x2 - mItemX;
+        final int deltaY = y2 - mItemY;
+
+        if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
+            if (mHitPos != MISS) {
+                if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
+                    startDrag(mHitPos, deltaX, deltaY);
+                }
+                else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled)
+                {
+                    mIsRemoving = true;
+                    startDrag(mFlingHitPos, deltaX, deltaY);
+                }
+            } else if (mFlingHitPos != MISS) {
+                if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
+                    mIsRemoving = true;
+                    startDrag(mFlingHitPos, deltaX, deltaY);
+                } else if (Math.abs(y2 - y1) > mTouchSlop) {
+                    mCanDrag = false; // if started to scroll the list then
+                                      // don't allow sorting nor fling-removing
+                }
+            }
+        }
+        // return whatever
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        // Log.d("mobeta", "lift listener long pressed");
+        if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
+            mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
+        }
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return false;
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public boolean onSingleTapUp(MotionEvent ev) {
+        if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+            if (mClickRemoveHitPos != MISS) {
+                mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
+            }
+        }
+        return true;
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public void onShowPress(MotionEvent ev) {
+        // do nothing
+    }
+
+    private GestureDetector.OnGestureListener mFlingRemoveListener =
+            new GestureDetector.SimpleOnGestureListener() {
+                @Override
+                public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                        float velocityY) {
+                    // Log.d("mobeta", "on fling remove called");
+                    if (mRemoveEnabled && mIsRemoving) {
+                        int w = mDslv.getWidth();
+                        int minPos = w / 5;
+                        if (velocityX > mFlingSpeed) {
+                            if (mPositionX > -minPos) {
+                                mDslv.stopDragWithVelocity(true, velocityX);
+                            }
+                        } else if (velocityX < -mFlingSpeed) {
+                            if (mPositionX < minPos) {
+                                mDslv.stopDragWithVelocity(true, velocityX);
+                            }
+                        }
+                        mIsRemoving = false;
+                    }
+                    return false;
+                }
+            };
+
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/DragSortCursorAdapter.java b/ring-android/src/cx/ring/views/dragsortlv/DragSortCursorAdapter.java
new file mode 100644
index 0000000..1700d44
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/DragSortCursorAdapter.java
@@ -0,0 +1,278 @@
+/*
+ *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ *
+ *  Part of the library: 
+ *  https://github.com/bauerca/drag-sort-listview
+ *  No longer maintained
+ *
+ *
+ */
+
+package cx.ring.views.dragsortlv;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.support.v4.widget.CursorAdapter;
+
+
+/**
+ * A subclass of {@link android.widget.CursorAdapter} that provides
+ * reordering of the elements in the Cursor based on completed
+ * drag-sort operations. The reordering is a simple mapping of
+ * list positions into Cursor positions (the Cursor is unchanged).
+ * To persist changes made by drag-sorts, one can retrieve the
+ * mapping with the {@link #getCursorPositions()} method, which
+ * returns the reordered list of Cursor positions.
+ *
+ * An instance of this class is passed
+ * to {@link DragSortListView#setAdapter(ListAdapter)} and, since
+ * this class implements the {@link DragSortListView.DragSortListener}
+ * interface, it is automatically set as the DragSortListener for
+ * the DragSortListView instance.
+ */
+public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
+
+    public static final int REMOVED = -1;
+
+    /**
+     * Key is ListView position, value is Cursor position
+     */
+    private SparseIntArray mListMapping = new SparseIntArray();
+
+    private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
+
+    public DragSortCursorAdapter(Context context, Cursor c) {
+        super(context, c);
+    }
+
+    public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
+        super(context, c, autoRequery);
+    }
+
+    public DragSortCursorAdapter(Context context, Cursor c, int flags) {
+        super(context, c, flags);
+    }
+
+    /**
+     * Swaps Cursor and clears list-Cursor mapping.
+     *
+     * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
+     */
+    @Override
+    public Cursor swapCursor(Cursor newCursor) {
+        Cursor old = super.swapCursor(newCursor);
+        resetMappings();
+        return old;
+    }
+
+    /**
+     * Changes Cursor and clears list-Cursor mapping.
+     *
+     * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
+     */
+    @Override
+    public void changeCursor(Cursor cursor) {
+        super.changeCursor(cursor);
+        resetMappings();
+    }
+
+    /**
+     * Resets list-cursor mapping.
+     */
+    public void reset() {
+        resetMappings();
+        notifyDataSetChanged();
+    }
+
+    private void resetMappings() {
+        mListMapping.clear();
+        mRemovedCursorPositions.clear();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return super.getItem(mListMapping.get(position, position));
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return super.getItemId(mListMapping.get(position, position));
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return super.getView(mListMapping.get(position, position), convertView, parent);
+    }
+
+    /**
+     * On drop, this updates the mapping between Cursor positions
+     * and ListView positions. The Cursor is unchanged. Retrieve
+     * the current mapping with {@link getCursorPositions()}.
+     *
+     * @see DragSortListView.DropListener#drop(int, int)
+     */
+    @Override
+    public void drop(int from, int to) {
+        if (from != to) {
+            int cursorFrom = mListMapping.get(from, from);
+
+            if (from > to) {
+                for (int i = from; i > to; --i) {
+                    mListMapping.put(i, mListMapping.get(i - 1, i - 1));
+                }
+            } else {
+                for (int i = from; i < to; ++i) {
+                    mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+                }
+            }
+            mListMapping.put(to, cursorFrom);
+
+            cleanMapping();        
+            notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * On remove, this updates the mapping between Cursor positions
+     * and ListView positions. The Cursor is unchanged. Retrieve
+     * the current mapping with {@link getCursorPositions()}.
+     *
+     * @see DragSortListView.RemoveListener#remove(int)
+     */
+    @Override
+    public void remove(int which) {
+        int cursorPos = mListMapping.get(which, which);
+        if (!mRemovedCursorPositions.contains(cursorPos)) {
+            mRemovedCursorPositions.add(cursorPos);
+        }
+
+        int newCount = getCount();
+        for (int i = which; i < newCount; ++i) {
+            mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+        }
+
+        mListMapping.delete(newCount);
+
+        cleanMapping();
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Does nothing. Just completes DragSortListener interface.
+     */
+    @Override
+    public void drag(int from, int to) {
+        // do nothing
+    }
+
+    /**
+     * Remove unnecessary mappings from sparse array.
+     */
+    private void cleanMapping() {
+        ArrayList<Integer> toRemove = new ArrayList<Integer>();
+
+        int size = mListMapping.size();
+        for (int i = 0; i < size; ++i) {
+            if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
+                toRemove.add(mListMapping.keyAt(i));
+            }
+        }
+
+        size = toRemove.size();
+        for (int i = 0; i < size; ++i) {
+            mListMapping.delete(toRemove.get(i));
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return super.getCount() - mRemovedCursorPositions.size();
+    }
+
+    /**
+     * Get the Cursor position mapped to by the provided list position
+     * (given all previously handled drag-sort
+     * operations).
+     *
+     * @param position List position
+     *
+     * @return The mapped-to Cursor position
+     */
+    public int getCursorPosition(int position) {
+        return mListMapping.get(position, position);
+    }
+
+    /**
+     * Get the current order of Cursor positions presented by the
+     * list.
+     */
+    public ArrayList<Integer> getCursorPositions() {
+        ArrayList<Integer> result = new ArrayList<Integer>();
+
+        for (int i = 0; i < getCount(); ++i) {
+            result.add(mListMapping.get(i, i));
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the list position mapped to by the provided Cursor position.
+     * If the provided Cursor position has been removed by a drag-sort,
+     * this returns {@link #REMOVED}.
+     *
+     * @param cursorPosition A Cursor position
+     * @return The mapped-to list position or REMOVED
+     */
+    public int getListPosition(int cursorPosition) {
+        if (mRemovedCursorPositions.contains(cursorPosition)) {
+            return REMOVED;
+        }
+
+        int index = mListMapping.indexOfValue(cursorPosition);
+        if (index < 0) {
+            return cursorPosition;
+        } else {
+            return mListMapping.keyAt(index);
+        }
+    }
+
+
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/DragSortItemView.java b/ring-android/src/cx/ring/views/dragsortlv/DragSortItemView.java
new file mode 100644
index 0000000..0bb976c
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/DragSortItemView.java
@@ -0,0 +1,135 @@
+/*
+ *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ *
+ *  Part of the library: 
+ *  https://github.com/bauerca/drag-sort-listview
+ *  No longer maintained
+ *
+ *
+ */
+
+package cx.ring.views.dragsortlv;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+
+/**
+ * Lightweight ViewGroup that wraps list items obtained from user's
+ * ListAdapter. ItemView expects a single child that has a definite
+ * height (i.e. the child's layout height is not MATCH_PARENT).
+ * The width of
+ * ItemView will always match the width of its child (that is,
+ * the width MeasureSpec given to ItemView is passed directly
+ * to the child, and the ItemView measured width is set to the
+ * child's measured width). The height of ItemView can be anything;
+ * the 
+ * 
+ *
+ * The purpose of this class is to optimize slide
+ * shuffle animations.
+ */
+public class DragSortItemView extends ViewGroup {
+
+    private int mGravity = Gravity.TOP;
+
+    public DragSortItemView(Context context) {
+        super(context);
+
+        // always init with standard ListView layout params
+        setLayoutParams(new AbsListView.LayoutParams(
+                LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        //setClipChildren(true);
+    }
+
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
+    public int getGravity() {
+        return mGravity;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final View child = getChildAt(0);
+
+        if (child == null) {
+            return;
+        }
+
+        if (mGravity == Gravity.TOP) {
+            child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
+        } else {
+            child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
+        }
+    }
+
+    /**
+     * 
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        final View child = getChildAt(0);
+        if (child == null) {
+            setMeasuredDimension(0, width);
+            return;
+        }
+
+        if (child.isLayoutRequested()) {
+            // Always let child be as tall as it wants.
+            measureChild(child, widthMeasureSpec,
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        }
+
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            ViewGroup.LayoutParams lp = getLayoutParams();
+
+            if (lp.height > 0) {
+                height = lp.height;
+            } else {
+                height = child.getMeasuredHeight();
+            }
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/DragSortItemViewCheckable.java b/ring-android/src/cx/ring/views/dragsortlv/DragSortItemViewCheckable.java
new file mode 100644
index 0000000..c4a176f
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/DragSortItemViewCheckable.java
@@ -0,0 +1,87 @@
+/*
+ *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@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.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify this program, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ *
+ *  Part of the library: 
+ *  https://github.com/bauerca/drag-sort-listview
+ *  No longer maintained
+ *
+ *
+ */
+
+package cx.ring.views.dragsortlv;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.Checkable;
+
+/**
+ * Lightweight ViewGroup that wraps list items obtained from user's
+ * ListAdapter. ItemView expects a single child that has a definite
+ * height (i.e. the child's layout height is not MATCH_PARENT).
+ * The width of
+ * ItemView will always match the width of its child (that is,
+ * the width MeasureSpec given to ItemView is passed directly
+ * to the child, and the ItemView measured width is set to the
+ * child's measured width). The height of ItemView can be anything;
+ * the 
+ * 
+ *
+ * The purpose of this class is to optimize slide
+ * shuffle animations.
+ */
+public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
+
+    public DragSortItemViewCheckable(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isChecked() {
+        View child = getChildAt(0);
+        if (child instanceof Checkable)
+            return ((Checkable) child).isChecked();
+        else
+            return false;
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        View child = getChildAt(0);
+        if (child instanceof Checkable)
+            ((Checkable) child).setChecked(checked);
+    }
+
+    @Override
+    public void toggle() {
+        View child = getChildAt(0);
+        if (child instanceof Checkable)
+            ((Checkable) child).toggle();
+    }
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/DragSortListView.java b/ring-android/src/cx/ring/views/dragsortlv/DragSortListView.java
new file mode 100644
index 0000000..3655fb2
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/DragSortListView.java
@@ -0,0 +1,3074 @@
+/*
+ * DragSortListView.
+ *
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cx.ring.views.dragsortlv;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.Checkable;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import cx.ring.R;
+
+/**
+ * ListView subclass that mediates drag and drop resorting of items.
+ * 
+ * 
+ * @author heycosmo
+ *
+ */
+public class DragSortListView extends ListView {
+    
+    
+    /**
+     * The View that floats above the ListView and represents
+     * the dragged item.
+     */
+    private View mFloatView;
+
+    /**
+     * The float View location. First based on touch location
+     * and given deltaX and deltaY. Then restricted by callback
+     * to FloatViewManager.onDragFloatView(). Finally restricted
+     * by bounds of DSLV.
+     */
+    private Point mFloatLoc = new Point();
+
+    private Point mTouchLoc = new Point();
+
+    /**
+     * The middle (in the y-direction) of the floating View.
+     */
+    private int mFloatViewMid;
+
+    /**
+     * Flag to make sure float View isn't measured twice
+     */
+    private boolean mFloatViewOnMeasured = false;
+
+    /**
+     * Watch the Adapter for data changes. Cancel a drag if
+     * coincident with a change.
+     */ 
+    private DataSetObserver mObserver;
+
+    /**
+     * Transparency for the floating View (XML attribute).
+     */
+    private float mFloatAlpha = 1.0f;
+    private float mCurrFloatAlpha = 1.0f;
+
+    /**
+     * While drag-sorting, the current position of the floating
+     * View. If dropped, the dragged item will land in this position.
+     */
+    private int mFloatPos;
+
+    /**
+     * The first expanded ListView position that helps represent
+     * the drop slot tracking the floating View.
+     */
+    private int mFirstExpPos;
+
+    /**
+     * The second expanded ListView position that helps represent
+     * the drop slot tracking the floating View. This can equal
+     * mFirstExpPos if there is no slide shuffle occurring; otherwise
+     * it is equal to mFirstExpPos + 1.
+     */
+    private int mSecondExpPos;
+
+    /**
+     * Flag set if slide shuffling is enabled.
+     */
+    private boolean mAnimate = false;
+
+    /**
+     * The user dragged from this position.
+     */
+    private int mSrcPos;
+
+    /**
+     * Offset (in x) within the dragged item at which the user
+     * picked it up (or first touched down with the digitalis).
+     */
+    private int mDragDeltaX;
+
+    /**
+     * Offset (in y) within the dragged item at which the user
+     * picked it up (or first touched down with the digitalis).
+     */
+    private int mDragDeltaY;
+
+
+    /**
+     * The difference (in x) between screen coordinates and coordinates
+     * in this view.
+     */
+    private int mOffsetX;
+
+    /**
+     * The difference (in y) between screen coordinates and coordinates
+     * in this view.
+     */
+    private int mOffsetY;
+
+    /**
+     * A listener that receives callbacks whenever the floating View
+     * hovers over a new position.
+     */
+    private DragListener mDragListener;
+
+    /**
+     * A listener that receives a callback when the floating View
+     * is dropped.
+     */
+    private DropListener mDropListener;
+
+    /**
+     * A listener that receives a callback when the floating View
+     * (or more precisely the originally dragged item) is removed
+     * by one of the provided gestures.
+     */
+    private RemoveListener mRemoveListener;
+
+    /**
+     * Enable/Disable item dragging
+     * 
+     * @attr name dslv:drag_enabled
+     */
+    private boolean mDragEnabled = true;
+
+    /**
+     * Drag state enum.
+     */
+    private final static int IDLE = 0;
+    private final static int REMOVING = 1;
+    private final static int DROPPING = 2;
+    private final static int STOPPED = 3;
+    private final static int DRAGGING = 4;
+
+    private int mDragState = IDLE;
+
+    /**
+     * Height in pixels to which the originally dragged item
+     * is collapsed during a drag-sort. Currently, this value
+     * must be greater than zero.
+     */
+    private int mItemHeightCollapsed = 1;
+
+    /**
+     * Height of the floating View. Stored for the purpose of
+     * providing the tracking drop slot.
+     */
+    private int mFloatViewHeight;
+
+    /**
+     * Convenience member. See above.
+     */
+    private int mFloatViewHeightHalf;
+
+    /**
+     * Save the given width spec for use in measuring children
+     */
+    private int mWidthMeasureSpec = 0;
+
+    /**
+     * Sample Views ultimately used for calculating the height
+     * of ListView items that are off-screen.
+     */
+    private View[] mSampleViewTypes = new View[1];
+
+    /**
+     * Drag-scroll encapsulator!
+     */
+    private DragScroller mDragScroller;
+
+    /**
+     * Determines the start of the upward drag-scroll region
+     * at the top of the ListView. Specified by a fraction
+     * of the ListView height, thus screen resolution agnostic.
+     */
+    private float mDragUpScrollStartFrac = 1.0f / 3.0f;
+
+    /**
+     * Determines the start of the downward drag-scroll region
+     * at the bottom of the ListView. Specified by a fraction
+     * of the ListView height, thus screen resolution agnostic.
+     */
+    private float mDragDownScrollStartFrac = 1.0f / 3.0f;
+
+    /**
+     * The following are calculated from the above fracs.
+     */
+    private int mUpScrollStartY;
+    private int mDownScrollStartY;
+    private float mDownScrollStartYF;
+    private float mUpScrollStartYF;
+
+    /**
+     * Calculated from above above and current ListView height.
+     */
+    private float mDragUpScrollHeight;
+
+    /**
+     * Calculated from above above and current ListView height.
+     */
+    private float mDragDownScrollHeight;
+
+    /**
+     * Maximum drag-scroll speed in pixels per ms. Only used with
+     * default linear drag-scroll profile.
+     */
+    private float mMaxScrollSpeed = 0.5f;
+
+    /**
+     * Defines the scroll speed during a drag-scroll. User can
+     * provide their own; this default is a simple linear profile
+     * where scroll speed increases linearly as the floating View
+     * nears the top/bottom of the ListView.
+     */
+    private DragScrollProfile mScrollProfile = new DragScrollProfile() {
+        @Override
+        public float getSpeed(float w, long t) {
+            return mMaxScrollSpeed * w;
+        }
+    };
+
+    /**
+     * Current touch x.
+     */
+    private int mX;
+
+    /**
+     * Current touch y.
+     */
+    private int mY;
+
+    /**
+     * Last touch x.
+     */
+    private int mLastX;
+
+    /**
+     * Last touch y.
+     */
+    private int mLastY;
+
+    /**
+     * The touch y-coord at which drag started
+     */
+    private int mDragStartY;
+
+    /**
+     * Drag flag bit. Floating View can move in the positive
+     * x direction.
+     */
+    public final static int DRAG_POS_X = 0x1;
+
+    /**
+     * Drag flag bit. Floating View can move in the negative
+     * x direction.
+     */
+    public final static int DRAG_NEG_X = 0x2;
+
+    /**
+     * Drag flag bit. Floating View can move in the positive
+     * y direction. This is subtle. What this actually means is
+     * that, if enabled, the floating View can be dragged below its starting
+     * position. Remove in favor of upper-bounding item position?
+     */
+    public final static int DRAG_POS_Y = 0x4;
+
+    /**
+     * Drag flag bit. Floating View can move in the negative
+     * y direction. This is subtle. What this actually means is
+     * that the floating View can be dragged above its starting
+     * position. Remove in favor of lower-bounding item position?
+     */
+    public final static int DRAG_NEG_Y = 0x8;
+
+    /**
+     * Flags that determine limits on the motion of the
+     * floating View. See flags above.
+     */
+    private int mDragFlags = 0;
+
+    /**
+     * Last call to an on*TouchEvent was a call to
+     * onInterceptTouchEvent.
+     */
+    private boolean mLastCallWasIntercept = false;
+
+    /**
+     * A touch event is in progress.
+     */
+    private boolean mInTouchEvent = false;
+
+    /**
+     * Let the user customize the floating View.
+     */
+    private FloatViewManager mFloatViewManager = null;
+
+    /**
+     * Given to ListView to cancel its action when a drag-sort
+     * begins.
+     */
+    private MotionEvent mCancelEvent;
+
+    /**
+     * Enum telling where to cancel the ListView action when a
+     * drag-sort begins
+     */
+    private static final int NO_CANCEL = 0;
+    private static final int ON_TOUCH_EVENT = 1;
+    private static final int ON_INTERCEPT_TOUCH_EVENT = 2;
+
+    /**
+     * Where to cancel the ListView action when a
+     * drag-sort begins
+     */ 
+    private int mCancelMethod = NO_CANCEL;
+
+    /**
+     * Determines when a slide shuffle animation starts. That is,
+     * defines how close to the edge of the drop slot the floating
+     * View must be to initiate the slide.
+     */
+    private float mSlideRegionFrac = 0.25f;
+
+    /**
+     * Number between 0 and 1 indicating the relative location of
+     * a sliding item (only used if drag-sort animations
+     * are turned on). Nearly 1 means the item is 
+     * at the top of the slide region (nearly full blank item
+     * is directly below).
+     */
+    private float mSlideFrac = 0.0f;
+
+    /**
+     * Wraps the user-provided ListAdapter. This is used to wrap each
+     * item View given by the user inside another View (currenly
+     * a RelativeLayout) which
+     * expands and collapses to simulate the item shuffling.
+     */
+    private AdapterWrapper mAdapterWrapper;
+
+    /**
+     * Turn on custom debugger.
+     */
+    private boolean mTrackDragSort = false;
+
+    /**
+     * Debugging class.
+     */
+    private DragSortTracker mDragSortTracker;
+
+    /**
+     * Needed for adjusting item heights from within layoutChildren
+     */
+    private boolean mBlockLayoutRequests = false;
+
+    /**
+     * Set to true when a down event happens during drag sort;
+     * for example, when drag finish animations are
+     * playing.
+     */
+    private boolean mIgnoreTouchEvent = false;
+
+    /**
+     * Caches DragSortItemView child heights. Sometimes DSLV has to
+     * know the height of an offscreen item. Since ListView virtualizes
+     * these, DSLV must get the item from the ListAdapter to obtain
+     * its height. That process can be expensive, but often the same
+     * offscreen item will be requested many times in a row. Once an
+     * offscreen item height is calculated, we cache it in this guy.
+     * Actually, we cache the height of the child of the
+     * DragSortItemView since the item height changes often during a
+     * drag-sort.
+     */
+    private static final int sCacheSize = 3;
+    private HeightCache mChildHeightCache = new HeightCache(sCacheSize);
+
+    private RemoveAnimator mRemoveAnimator;
+
+    private LiftAnimator mLiftAnimator;
+
+    private DropAnimator mDropAnimator;
+
+    private boolean mUseRemoveVelocity;
+    private float mRemoveVelocityX = 0;
+
+    public DragSortListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        int defaultDuration = 150;
+        int removeAnimDuration = defaultDuration; // ms
+        int dropAnimDuration = defaultDuration; // ms
+
+        if (attrs != null) {
+            TypedArray a = getContext().obtainStyledAttributes(attrs,
+                    R.styleable.DragSortListView, 0, 0);
+
+            mItemHeightCollapsed = Math.max(1, a.getDimensionPixelSize(
+                    R.styleable.DragSortListView_collapsed_height, 1));
+
+            mTrackDragSort = a.getBoolean(
+                    R.styleable.DragSortListView_track_drag_sort, false);
+
+            if (mTrackDragSort) {
+                mDragSortTracker = new DragSortTracker();
+            }
+
+            // alpha between 0 and 255, 0=transparent, 255=opaque
+            mFloatAlpha = a.getFloat(R.styleable.DragSortListView_float_alpha, mFloatAlpha);
+            mCurrFloatAlpha = mFloatAlpha;
+
+            mDragEnabled = a.getBoolean(R.styleable.DragSortListView_drag_enabled, mDragEnabled);
+
+            mSlideRegionFrac = Math.max(0.0f,
+                    Math.min(1.0f, 1.0f - a.getFloat(
+                            R.styleable.DragSortListView_slide_shuffle_speed,
+                            0.75f)));
+
+            mAnimate = mSlideRegionFrac > 0.0f;
+
+            float frac = a.getFloat(
+                    R.styleable.DragSortListView_drag_scroll_start,
+                    mDragUpScrollStartFrac);
+
+            setDragScrollStart(frac);
+
+            mMaxScrollSpeed = a.getFloat(
+                    R.styleable.DragSortListView_max_drag_scroll_speed,
+                    mMaxScrollSpeed);
+
+            removeAnimDuration = a.getInt(
+                    R.styleable.DragSortListView_remove_animation_duration,
+                    removeAnimDuration);
+
+            dropAnimDuration = a.getInt(
+                    R.styleable.DragSortListView_drop_animation_duration,
+                    dropAnimDuration);
+
+            boolean useDefault = a.getBoolean(
+                    R.styleable.DragSortListView_use_default_controller,
+                    true);
+
+            if (useDefault) {
+                boolean removeEnabled = a.getBoolean(
+                        R.styleable.DragSortListView_remove_enabled,
+                        false);
+                int removeMode = a.getInt(
+                        R.styleable.DragSortListView_remove_mode,
+                        DragSortController.FLING_REMOVE);
+                boolean sortEnabled = a.getBoolean(
+                        R.styleable.DragSortListView_sort_enabled,
+                        true);
+                int dragInitMode = a.getInt(
+                        R.styleable.DragSortListView_drag_start_mode,
+                        DragSortController.ON_DOWN);
+                int dragHandleId = a.getResourceId(
+                        R.styleable.DragSortListView_drag_handle_id,
+                        0);
+                int flingHandleId = a.getResourceId(
+                        R.styleable.DragSortListView_fling_handle_id,
+                        0);
+                int clickRemoveId = a.getResourceId(
+                        R.styleable.DragSortListView_click_remove_id,
+                        0);
+                int bgColor = a.getColor(
+                        R.styleable.DragSortListView_float_background_color,
+                        Color.BLACK);
+
+                DragSortController controller = new DragSortController(
+                        this, dragHandleId, dragInitMode, removeMode,
+                        clickRemoveId, flingHandleId);
+                controller.setRemoveEnabled(removeEnabled);
+                controller.setSortEnabled(sortEnabled);
+                controller.setBackgroundColor(bgColor);
+
+                mFloatViewManager = controller;
+                setOnTouchListener(controller);
+            }
+
+            a.recycle();
+        }
+
+        mDragScroller = new DragScroller();
+
+        float smoothness = 0.5f;
+        if (removeAnimDuration > 0) {
+            mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration);
+        }
+        // mLiftAnimator = new LiftAnimator(smoothness, 100);
+        if (dropAnimDuration > 0) {
+            mDropAnimator = new DropAnimator(smoothness, dropAnimDuration);
+        }
+
+        mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
+                0f, 0, 0);
+
+        // construct the dataset observer
+        mObserver = new DataSetObserver() {
+            private void cancel() {
+                if (mDragState == DRAGGING) {
+                    cancelDrag();
+                }
+            }
+
+            @Override
+            public void onChanged() {
+                cancel();
+            }
+
+            @Override
+            public void onInvalidated() {
+                cancel();
+            }
+        };
+    }
+
+    /**
+     * Usually called from a FloatViewManager. The float alpha
+     * will be reset to the xml-defined value every time a drag
+     * is stopped.
+     */
+    public void setFloatAlpha(float alpha) {
+        mCurrFloatAlpha = alpha;
+    }
+
+    public float getFloatAlpha() {
+        return mCurrFloatAlpha;
+    }
+
+    /**
+     * Set maximum drag scroll speed in positions/second. Only applies
+     * if using default ScrollSpeedProfile.
+     * 
+     * @param max Maximum scroll speed.
+     */
+    public void setMaxScrollSpeed(float max) {
+        mMaxScrollSpeed = max;
+    }
+
+    /**
+     * For each DragSortListView Listener interface implemented by
+     * <code>adapter</code>, this method calls the appropriate
+     * set*Listener method with <code>adapter</code> as the argument.
+     * 
+     * @param adapter The ListAdapter providing data to back
+     * DragSortListView.
+     *
+     * @see android.widget.ListView#setAdapter(android.widget.ListAdapter)
+     */
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (adapter != null) {
+            mAdapterWrapper = new AdapterWrapper(adapter);
+            adapter.registerDataSetObserver(mObserver);
+
+            if (adapter instanceof DropListener) {
+                setDropListener((DropListener) adapter);
+            }
+            if (adapter instanceof DragListener) {
+                setDragListener((DragListener) adapter);
+            }
+            if (adapter instanceof RemoveListener) {
+                setRemoveListener((RemoveListener) adapter);
+            }
+        } else {
+            mAdapterWrapper = null;
+        }
+
+        super.setAdapter(mAdapterWrapper);
+    }
+
+    /**
+     * As opposed to {@link ListView#getAdapter()}, which returns
+     * a heavily wrapped ListAdapter (DragSortListView wraps the
+     * input ListAdapter {\emph and} ListView wraps the wrapped one).
+     *
+     * @return The ListAdapter set as the argument of {@link setAdapter()}
+     */
+    public ListAdapter getInputAdapter() {
+        if (mAdapterWrapper == null) {
+            return null;
+        } else {
+            return mAdapterWrapper.getAdapter();
+        }
+    }
+
+    private class AdapterWrapper extends BaseAdapter {
+        private ListAdapter mAdapter;
+
+        public AdapterWrapper(ListAdapter adapter) {
+            super();
+            mAdapter = adapter;
+            
+            mAdapter.registerDataSetObserver(new DataSetObserver() {
+                public void onChanged() {
+                    notifyDataSetChanged();
+                }
+
+                public void onInvalidated() {
+                    notifyDataSetInvalidated();
+                }
+            });
+        }
+
+        public ListAdapter getAdapter() {
+            return mAdapter;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mAdapter.getItemId(position);
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mAdapter.getItem(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mAdapter.getCount();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return mAdapter.areAllItemsEnabled();
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return mAdapter.isEnabled(position);
+        }
+        
+        @Override
+        public int getItemViewType(int position) {
+            return mAdapter.getItemViewType(position);
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return mAdapter.getViewTypeCount();
+        }
+        
+        @Override
+        public boolean hasStableIds() {
+            return mAdapter.hasStableIds();
+        }
+        
+        @Override
+        public boolean isEmpty() {
+            return mAdapter.isEmpty();
+        }
+
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+
+            DragSortItemView v;
+            View child;
+            // Log.d("mobeta",
+            // "getView: position="+position+" convertView="+convertView);
+            if (convertView != null) {
+                v = (DragSortItemView) convertView;
+                View oldChild = v.getChildAt(0);
+
+                child = mAdapter.getView(position, oldChild, DragSortListView.this);
+                if (child != oldChild) {
+                    // shouldn't get here if user is reusing convertViews
+                    // properly
+                    if (oldChild != null) {
+                        v.removeViewAt(0);
+                    }
+                    v.addView(child);
+                }
+            } else {
+                child = mAdapter.getView(position, null, DragSortListView.this);
+                if (child instanceof Checkable) {
+                    v = new DragSortItemViewCheckable(getContext());
+                } else {
+                    v = new DragSortItemView(getContext());
+                }
+                v.setLayoutParams(new AbsListView.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
+                v.addView(child);
+            }
+
+            // Set the correct item height given drag state; passed
+            // View needs to be measured if measurement is required.
+            adjustItem(position + getHeaderViewsCount(), v, true);
+
+            return v;
+        }
+    }
+
+    private void drawDivider(int expPosition, Canvas canvas) {
+
+        final Drawable divider = getDivider();
+        final int dividerHeight = getDividerHeight();
+        // Log.d("mobeta", "div="+divider+" divH="+dividerHeight);
+
+        if (divider != null && dividerHeight != 0) {
+            final ViewGroup expItem = (ViewGroup) getChildAt(expPosition
+                    - getFirstVisiblePosition());
+            if (expItem != null) {
+                final int l = getPaddingLeft();
+                final int r = getWidth() - getPaddingRight();
+                final int t;
+                final int b;
+
+                final int childHeight = expItem.getChildAt(0).getHeight();
+
+                if (expPosition > mSrcPos) {
+                    t = expItem.getTop() + childHeight;
+                    b = t + dividerHeight;
+                } else {
+                    b = expItem.getBottom() - childHeight;
+                    t = b - dividerHeight;
+                }
+                // Log.d("mobeta", "l="+l+" t="+t+" r="+r+" b="+b);
+
+                // Have to clip to support ColorDrawable on <= Gingerbread
+                canvas.save();
+                canvas.clipRect(l, t, r, b);
+                divider.setBounds(l, t, r, b);
+                divider.draw(canvas);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mDragState != IDLE) {
+            // draw the divider over the expanded item
+            if (mFirstExpPos != mSrcPos) {
+                drawDivider(mFirstExpPos, canvas);
+            }
+            if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
+                drawDivider(mSecondExpPos, canvas);
+            }
+        }
+
+        if (mFloatView != null) {
+            // draw the float view over everything
+            final int w = mFloatView.getWidth();
+            final int h = mFloatView.getHeight();
+
+            int x = mFloatLoc.x;
+
+            int width = getWidth();
+            if (x < 0)
+                x = -x;
+            float alphaMod;
+            if (x < width) {
+                alphaMod = ((float) (width - x)) / ((float) width);
+                alphaMod *= alphaMod;
+            } else {
+                alphaMod = 0;
+            }
+
+            final int alpha = (int) (255f * mCurrFloatAlpha * alphaMod);
+
+            canvas.save();
+            // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
+            canvas.translate(mFloatLoc.x, mFloatLoc.y);
+            canvas.clipRect(0, 0, w, h);
+
+            // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
+            canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
+            mFloatView.draw(canvas);
+            canvas.restore();
+            canvas.restore();
+        }
+    }
+
+    private int getItemHeight(int position) {
+        View v = getChildAt(position - getFirstVisiblePosition());
+
+        if (v != null) {
+            // item is onscreen, just get the height of the View
+            return v.getHeight();
+        } else {
+            // item is offscreen. get child height and calculate
+            // item height based on current shuffle state
+            return calcItemHeight(position, getChildHeight(position));
+        }
+    }
+
+    private void printPosData() {
+        Log.d("mobeta", "mSrcPos=" + mSrcPos + " mFirstExpPos=" + mFirstExpPos + " mSecondExpPos="
+                + mSecondExpPos);
+    }
+
+    private class HeightCache {
+
+        private SparseIntArray mMap;
+        private ArrayList<Integer> mOrder;
+        private int mMaxSize;
+
+        public HeightCache(int size) {
+            mMap = new SparseIntArray(size);
+            mOrder = new ArrayList<Integer>(size);
+            mMaxSize = size;
+        }
+
+        /**
+         * Add item height at position if doesn't already exist.
+         */
+        public void add(int position, int height) {
+            int currHeight = mMap.get(position, -1);
+            if (currHeight != height) {
+                if (currHeight == -1) {
+                    if (mMap.size() == mMaxSize) {
+                        // remove oldest entry
+                        mMap.delete(mOrder.remove(0));
+                    }
+                } else {
+                    // move position to newest slot
+                    mOrder.remove((Integer) position);
+                }
+                mMap.put(position, height);
+                mOrder.add(position);
+            }
+        }
+
+        public int get(int position) {
+            return mMap.get(position, -1);
+        }
+
+        public void clear() {
+            mMap.clear();
+            mOrder.clear();
+        }
+
+    }
+
+    /**
+     * Get the shuffle edge for item at position when top of
+     * item is at y-coord top. Assumes that current item heights
+     * are consistent with current float view location and
+     * thus expanded positions and slide fraction. i.e. Should not be
+     * called between update of expanded positions/slide fraction
+     * and layoutChildren.
+     *
+     * @param position 
+     * @param top
+     * calculates this height.
+     *
+     * @return Shuffle line between position-1 and position (for
+     * the given view of the list; that is, for when top of item at
+     * position has y-coord of given `top`). If
+     * floating View (treated as horizontal line) is dropped
+     * immediately above this line, it lands in position-1. If
+     * dropped immediately below this line, it lands in position.
+     */
+    private int getShuffleEdge(int position, int top) {
+
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+
+        // shuffle edges are defined between items that can be
+        // dragged; there are N-1 of them if there are N draggable
+        // items.
+
+        if (position <= numHeaders || (position >= getCount() - numFooters)) {
+            return top;
+        }
+
+        int divHeight = getDividerHeight();
+
+        int edge;
+
+        int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+        int childHeight = getChildHeight(position);
+        int itemHeight = getItemHeight(position);
+
+        // first calculate top of item given that floating View is
+        // centered over src position
+        int otop = top;
+        if (mSecondExpPos <= mSrcPos) {
+            // items are expanded on and/or above the source position
+
+            if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+                if (position == mSrcPos) {
+                    otop = top + itemHeight - mFloatViewHeight;
+                } else {
+                    int blankHeight = itemHeight - childHeight;
+                    otop = top + blankHeight - maxBlankHeight;
+                }
+            } else if (position > mSecondExpPos && position <= mSrcPos) {
+                otop = top - maxBlankHeight;
+            }
+
+        } else {
+            // items are expanded on and/or below the source position
+
+            if (position > mSrcPos && position <= mFirstExpPos) {
+                otop = top + maxBlankHeight;
+            } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+                int blankHeight = itemHeight - childHeight;
+                otop = top + blankHeight;
+            }
+        }
+
+        // otop is set
+        if (position <= mSrcPos) {
+            edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2;
+        } else {
+            edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2;
+        }
+
+        return edge;
+    }
+
+    private boolean updatePositions() {
+
+        final int first = getFirstVisiblePosition();
+        int startPos = mFirstExpPos;
+        View startView = getChildAt(startPos - first);
+
+        if (startView == null) {
+            startPos = first + getChildCount() / 2;
+            startView = getChildAt(startPos - first);
+        }
+        int startTop = startView.getTop();
+
+        int itemHeight = startView.getHeight();
+
+        int edge = getShuffleEdge(startPos, startTop);
+        int lastEdge = edge;
+
+        int divHeight = getDividerHeight();
+
+        // Log.d("mobeta", "float mid="+mFloatViewMid);
+
+        int itemPos = startPos;
+        int itemTop = startTop;
+        if (mFloatViewMid < edge) {
+            // scanning up for float position
+            // Log.d("mobeta", "    edge="+edge);
+            while (itemPos >= 0) {
+                itemPos--;
+                itemHeight = getItemHeight(itemPos);
+
+                if (itemPos == 0) {
+                    edge = itemTop - divHeight - itemHeight;
+                    break;
+                }
+
+                itemTop -= itemHeight + divHeight;
+                edge = getShuffleEdge(itemPos, itemTop);
+                // Log.d("mobeta", "    edge="+edge);
+
+                if (mFloatViewMid >= edge) {
+                    break;
+                }
+
+                lastEdge = edge;
+            }
+        } else {
+            // scanning down for float position
+            // Log.d("mobeta", "    edge="+edge);
+            final int count = getCount();
+            while (itemPos < count) {
+                if (itemPos == count - 1) {
+                    edge = itemTop + divHeight + itemHeight;
+                    break;
+                }
+
+                itemTop += divHeight + itemHeight;
+                itemHeight = getItemHeight(itemPos + 1);
+                edge = getShuffleEdge(itemPos + 1, itemTop);
+                // Log.d("mobeta", "    edge="+edge);
+
+                // test for hit
+                if (mFloatViewMid < edge) {
+                    break;
+                }
+
+                lastEdge = edge;
+                itemPos++;
+            }
+        }
+
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+
+        boolean updated = false;
+
+        int oldFirstExpPos = mFirstExpPos;
+        int oldSecondExpPos = mSecondExpPos;
+        float oldSlideFrac = mSlideFrac;
+
+        if (mAnimate) {
+            int edgeToEdge = Math.abs(edge - lastEdge);
+
+            int edgeTop, edgeBottom;
+            if (mFloatViewMid < edge) {
+                edgeBottom = edge;
+                edgeTop = lastEdge;
+            } else {
+                edgeTop = edge;
+                edgeBottom = lastEdge;
+            }
+            // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
+
+            int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge);
+            float slideRgnHeightF = (float) slideRgnHeight;
+            int slideEdgeTop = edgeTop + slideRgnHeight;
+            int slideEdgeBottom = edgeBottom - slideRgnHeight;
+
+            // Three regions
+            if (mFloatViewMid < slideEdgeTop) {
+                mFirstExpPos = itemPos - 1;
+                mSecondExpPos = itemPos;
+                mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF;
+                // Log.d("mobeta",
+                // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
+            } else if (mFloatViewMid < slideEdgeBottom) {
+                mFirstExpPos = itemPos;
+                mSecondExpPos = itemPos;
+            } else {
+                mFirstExpPos = itemPos;
+                mSecondExpPos = itemPos + 1;
+                mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid))
+                        / slideRgnHeightF);
+                // Log.d("mobeta",
+                // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
+            }
+
+        } else {
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        }
+
+        // correct for headers and footers
+        if (mFirstExpPos < numHeaders) {
+            itemPos = numHeaders;
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        } else if (mSecondExpPos >= getCount() - numFooters) {
+            itemPos = getCount() - numFooters - 1;
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        }
+
+        if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
+                || mSlideFrac != oldSlideFrac) {
+            updated = true;
+        }
+
+        if (itemPos != mFloatPos) {
+            if (mDragListener != null) {
+                mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
+            }
+
+            mFloatPos = itemPos;
+            updated = true;
+        }
+
+        return updated;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mTrackDragSort) {
+            mDragSortTracker.appendState();
+        }
+    }
+
+    private class SmoothAnimator implements Runnable {
+        protected long mStartTime;
+
+        private float mDurationF;
+
+        private float mAlpha;
+        private float mA, mB, mC, mD;
+
+        private boolean mCanceled;
+
+        public SmoothAnimator(float smoothness, int duration) {
+            mAlpha = smoothness;
+            mDurationF = (float) duration;
+            mA = mD = 1f / (2f * mAlpha * (1f - mAlpha));
+            mB = mAlpha / (2f * (mAlpha - 1f));
+            mC = 1f / (1f - mAlpha);
+        }
+
+        public float transform(float frac) {
+            if (frac < mAlpha) {
+                return mA * frac * frac;
+            } else if (frac < 1f - mAlpha) {
+                return mB + mC * frac;
+            } else {
+                return 1f - mD * (frac - 1f) * (frac - 1f);
+            }
+        }
+
+        public void start() {
+            mStartTime = SystemClock.uptimeMillis();
+            mCanceled = false;
+            onStart();
+            post(this);
+        }
+
+        public void cancel() {
+            mCanceled = true;
+        }
+
+        public void onStart() {
+            // stub
+        }
+
+        public void onUpdate(float frac, float smoothFrac) {
+            // stub
+        }
+
+        public void onStop() {
+            // stub
+        }
+
+        @Override
+        public void run() {
+            if (mCanceled) {
+                return;
+            }
+
+            float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF;
+
+            if (fraction >= 1f) {
+                onUpdate(1f, 1f);
+                onStop();
+            } else {
+                onUpdate(fraction, transform(fraction));
+                post(this);
+            }
+        }
+    }
+
+    /**
+     * Centers floating View under touch point.
+     */
+    private class LiftAnimator extends SmoothAnimator {
+
+        private float mInitDragDeltaY;
+        private float mFinalDragDeltaY;
+
+        public LiftAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mInitDragDeltaY = mDragDeltaY;
+            mFinalDragDeltaY = mFloatViewHeightHalf;
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            if (mDragState != DRAGGING) {
+                cancel();
+            } else {
+                mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac)
+                        * mInitDragDeltaY);
+                mFloatLoc.y = mY - mDragDeltaY;
+                doDragFloatView(true);
+            }
+        }
+    }
+
+    /**
+     * Centers floating View over drop slot before destroying.
+     */
+    private class DropAnimator extends SmoothAnimator {
+
+        private int mDropPos;
+        private int srcPos;
+        private float mInitDeltaY;
+        private float mInitDeltaX;
+
+        public DropAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mDropPos = mFloatPos;
+            srcPos = mSrcPos;
+            mDragState = DROPPING;
+            mInitDeltaY = mFloatLoc.y - getTargetY();
+            mInitDeltaX = mFloatLoc.x - getPaddingLeft();
+        }
+
+        private int getTargetY() {
+            final int first = getFirstVisiblePosition();
+            final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2;
+            View v = getChildAt(mDropPos - first);
+            int targetY = -1;
+            if (v != null) {
+                if (mDropPos == srcPos) {
+                    targetY = v.getTop();
+                } else if (mDropPos < srcPos) {
+                    // expanded down
+                    targetY = v.getTop() - otherAdjust;
+                } else {
+                    // expanded up
+                    targetY = v.getBottom() + otherAdjust - mFloatViewHeight;
+                }
+            } else {
+                // drop position is not on screen?? no animation
+                cancel();
+            }
+
+            return targetY;
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            final int targetY = getTargetY();
+            final int targetX = getPaddingLeft();
+            final float deltaY = mFloatLoc.y - targetY;
+            final float deltaX = mFloatLoc.x - targetX;
+            final float f = 1f - smoothFrac;
+            if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) {
+                mFloatLoc.y = targetY + (int) (mInitDeltaY * f);
+                mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f);
+                doDragFloatView(true);
+            }
+        }
+
+        @Override
+        public void onStop() {
+            dropFloatView();
+        }
+
+    }
+
+    /**
+     * Collapses expanded items.
+     */
+    private class RemoveAnimator extends SmoothAnimator {
+
+        private float mFloatLocX;
+        private float mFirstStartBlank;
+        private float mSecondStartBlank;
+
+        private int mFirstChildHeight = -1;
+        private int mSecondChildHeight = -1;
+
+        private int mFirstPos;
+        private int mSecondPos;
+        private int srcPos;
+
+        public RemoveAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mFirstChildHeight = -1;
+            mSecondChildHeight = -1;
+            mFirstPos = mFirstExpPos;
+            mSecondPos = mSecondExpPos;
+            srcPos = mSrcPos;
+            mDragState = REMOVING;
+
+            mFloatLocX = mFloatLoc.x;
+            if (mUseRemoveVelocity) {
+                float minVelocity = 2f * getWidth();
+                if (mRemoveVelocityX == 0) {
+                    mRemoveVelocityX = (mFloatLocX < 0 ? -1 : 1) * minVelocity;
+                } else {
+                    minVelocity *= 2;
+                    if (mRemoveVelocityX < 0 && mRemoveVelocityX > -minVelocity)
+                        mRemoveVelocityX = -minVelocity;
+                    else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity)
+                        mRemoveVelocityX = minVelocity;
+                }
+            } else {
+                destroyFloatView();
+            }
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            float f = 1f - smoothFrac;
+
+            final int firstVis = getFirstVisiblePosition();
+            View item = getChildAt(mFirstPos - firstVis);
+            ViewGroup.LayoutParams lp;
+            int blank;
+
+            if (mUseRemoveVelocity) {
+                float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000;
+                if (dt == 0)
+                    return;
+                float dx = mRemoveVelocityX * dt;
+                int w = getWidth();
+                mRemoveVelocityX += (mRemoveVelocityX > 0 ? 1 : -1) * dt * w;
+                mFloatLocX += dx;
+                mFloatLoc.x = (int) mFloatLocX;
+                if (mFloatLocX < w && mFloatLocX > -w) {
+                    mStartTime = SystemClock.uptimeMillis();
+                    doDragFloatView(true);
+                    return;
+                }
+            }
+
+            if (item != null) {
+                if (mFirstChildHeight == -1) {
+                    mFirstChildHeight = getChildHeight(mFirstPos, item, false);
+                    mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight);
+                }
+                blank = Math.max((int) (f * mFirstStartBlank), 1);
+                lp = item.getLayoutParams();
+                lp.height = mFirstChildHeight + blank;
+                item.setLayoutParams(lp);
+            }
+            if (mSecondPos != mFirstPos) {
+                item = getChildAt(mSecondPos - firstVis);
+                if (item != null) {
+                    if (mSecondChildHeight == -1) {
+                        mSecondChildHeight = getChildHeight(mSecondPos, item, false);
+                        mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight);
+                    }
+                    blank = Math.max((int) (f * mSecondStartBlank), 1);
+                    lp = item.getLayoutParams();
+                    lp.height = mSecondChildHeight + blank;
+                    item.setLayoutParams(lp);
+                }
+            }
+        }
+
+        @Override
+        public void onStop() {
+            doRemoveItem();
+        }
+    }
+
+    public void removeItem(int which) {
+
+        mUseRemoveVelocity = false;
+        removeItem(which, 0);
+    }
+
+    /**
+     * Removes an item from the list and animates the removal.
+     *
+     * @param which Position to remove (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     * @param velocityX 
+     */
+    public void removeItem(int which, float velocityX) {
+        if (mDragState == IDLE || mDragState == DRAGGING) {
+
+            if (mDragState == IDLE) {
+                // called from outside drag-sort
+                mSrcPos = getHeaderViewsCount() + which;
+                mFirstExpPos = mSrcPos;
+                mSecondExpPos = mSrcPos;
+                mFloatPos = mSrcPos;
+                View v = getChildAt(mSrcPos - getFirstVisiblePosition());
+                if (v != null) {
+                    v.setVisibility(View.INVISIBLE);
+                }
+            }
+
+            mDragState = REMOVING;
+            mRemoveVelocityX = velocityX;
+
+            if (mInTouchEvent) {
+                switch (mCancelMethod) {
+                    case ON_TOUCH_EVENT:
+                        super.onTouchEvent(mCancelEvent);
+                        break;
+                    case ON_INTERCEPT_TOUCH_EVENT:
+                        super.onInterceptTouchEvent(mCancelEvent);
+                        break;
+                }
+            }
+
+            if (mRemoveAnimator != null) {
+                mRemoveAnimator.start();
+            } else {
+                doRemoveItem(which);
+            }
+        }
+    }
+
+    /**
+     * Move an item, bypassing the drag-sort process. Simply calls
+     * through to {@link DropListener#drop(int, int)}.
+     * 
+     * @param from Position to move (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     * @param to Target position (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     */
+    public void moveItem(int from, int to) {
+        if (mDropListener != null) {
+            final int count = getInputAdapter().getCount();
+            if (from >= 0 && from < count && to >= 0 && to < count) {
+                mDropListener.drop(from, to);
+            }
+        }
+    }
+
+    /**
+     * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with
+     * <code>true</code> as the first argument.
+     */
+    public void cancelDrag() {
+        if (mDragState == DRAGGING) {
+            mDragScroller.stopScrolling(true);
+            destroyFloatView();
+            clearPositions();
+            adjustAllItems();
+
+            if (mInTouchEvent) {
+                mDragState = STOPPED;
+            } else {
+                mDragState = IDLE;
+            }
+        }
+    }
+
+    private void clearPositions() {
+        mSrcPos = -1;
+        mFirstExpPos = -1;
+        mSecondExpPos = -1;
+        mFloatPos = -1;
+    }
+
+    private void dropFloatView() {
+        // must set to avoid cancelDrag being called from the
+        // DataSetObserver
+        mDragState = DROPPING;
+
+        if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
+            final int numHeaders = getHeaderViewsCount();
+            mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
+        }
+
+        destroyFloatView();
+
+        adjustOnReorder();
+        clearPositions();
+        adjustAllItems();
+
+        // now the drag is done
+        if (mInTouchEvent) {
+            mDragState = STOPPED;
+        } else {
+            mDragState = IDLE;
+        }
+    }
+
+    private void doRemoveItem() {
+        doRemoveItem(mSrcPos - getHeaderViewsCount());
+    }
+
+    /**
+     * Removes dragged item from the list. Calls RemoveListener.
+     */
+    private void doRemoveItem(int which) {
+        // must set to avoid cancelDrag being called from the
+        // DataSetObserver
+        mDragState = REMOVING;
+
+        // end it
+        if (mRemoveListener != null) {
+            mRemoveListener.remove(which);
+        }
+
+        destroyFloatView();
+
+        adjustOnReorder();
+        clearPositions();
+
+        // now the drag is done
+        if (mInTouchEvent) {
+            mDragState = STOPPED;
+        } else {
+            mDragState = IDLE;
+        }
+    }
+
+    private void adjustOnReorder() {
+        final int firstPos = getFirstVisiblePosition();
+        // Log.d("mobeta", "first="+firstPos+" src="+mSrcPos);
+        if (mSrcPos < firstPos) {
+            // collapsed src item is off screen;
+            // adjust the scroll after item heights have been fixed
+            View v = getChildAt(0);
+            int top = 0;
+            if (v != null) {
+                top = v.getTop();
+            }
+            // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
+            setSelectionFromTop(firstPos - 1, top - getPaddingTop());
+        }
+    }
+
+    /**
+     * Stop a drag in progress. Pass <code>true</code> if you would
+     * like to remove the dragged item from the list.
+     *
+     * @param remove Remove the dragged item from the list. Calls
+     * a registered RemoveListener, if one exists. Otherwise, calls
+     * the DropListener, if one exists.
+     *
+     * @return True if the stop was successful. False if there is
+     * no floating View.
+     */
+    public boolean stopDrag(boolean remove) {
+        mUseRemoveVelocity = false;
+        return stopDrag(remove, 0);
+    }
+
+    public boolean stopDragWithVelocity(boolean remove, float velocityX) {
+
+        mUseRemoveVelocity = true;
+        return stopDrag(remove, velocityX);
+    }
+
+    public boolean stopDrag(boolean remove, float velocityX) {
+        if (mFloatView != null) {
+            mDragScroller.stopScrolling(true);
+
+            if (remove) {
+                removeItem(mSrcPos - getHeaderViewsCount(), velocityX);
+            } else {
+                if (mDropAnimator != null) {
+                    mDropAnimator.start();
+                } else {
+                    dropFloatView();
+                }
+            }
+
+            if (mTrackDragSort) {
+                mDragSortTracker.stopTracking();
+            }
+
+            return true;
+        } else {
+            // stop failed
+            return false;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mIgnoreTouchEvent) {
+            mIgnoreTouchEvent = false;
+            return false;
+        }
+
+        if (!mDragEnabled) {
+            return super.onTouchEvent(ev);
+        }
+
+        boolean more = false;
+
+        boolean lastCallWasIntercept = mLastCallWasIntercept;
+        mLastCallWasIntercept = false;
+
+        if (!lastCallWasIntercept) {
+            saveTouchCoords(ev);
+        }
+
+        // if (mFloatView != null) {
+        if (mDragState == DRAGGING) {
+            onDragTouchEvent(ev);
+            more = true; // give us more!
+        } else {
+            // what if float view is null b/c we dropped in middle
+            // of drag touch event?
+
+            // if (mDragState != STOPPED) {
+            if (mDragState == IDLE) {
+                if (super.onTouchEvent(ev)) {
+                    more = true;
+                }
+            }
+
+            int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    doActionUpOrCancel();
+                    break;
+                default:
+                    if (more) {
+                        mCancelMethod = ON_TOUCH_EVENT;
+                    }
+            }
+        }
+
+        return more;
+    }
+
+    private void doActionUpOrCancel() {
+        mCancelMethod = NO_CANCEL;
+        mInTouchEvent = false;
+        if (mDragState == STOPPED) {
+            mDragState = IDLE;
+        }
+        mCurrFloatAlpha = mFloatAlpha;
+        mListViewIntercepted = false;
+        mChildHeightCache.clear();
+    }
+
+    private void saveTouchCoords(MotionEvent ev) {
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+        if (action != MotionEvent.ACTION_DOWN) {
+            mLastX = mX;
+            mLastY = mY;
+        }
+        mX = (int) ev.getX();
+        mY = (int) ev.getY();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mLastX = mX;
+            mLastY = mY;
+        }
+        mOffsetX = (int) ev.getRawX() - mX;
+        mOffsetY = (int) ev.getRawY() - mY;
+    }
+
+    public boolean listViewIntercepted() {
+        return mListViewIntercepted;
+    }
+
+    private boolean mListViewIntercepted = false;
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (!mDragEnabled) {
+            return super.onInterceptTouchEvent(ev);
+        }
+
+        saveTouchCoords(ev);
+        mLastCallWasIntercept = true;
+
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (mDragState != IDLE) {
+                // intercept and ignore
+                mIgnoreTouchEvent = true;
+                return true;
+            }
+            mInTouchEvent = true;
+        }
+
+        boolean intercept = false;
+
+        // the following deals with calls to super.onInterceptTouchEvent
+        if (mFloatView != null) {
+            // super's touch event canceled in startDrag
+            intercept = true;
+        } else {
+            if (super.onInterceptTouchEvent(ev)) {
+                mListViewIntercepted = true;
+                intercept = true;
+            }
+
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    doActionUpOrCancel();
+                    break;
+                default:
+                    if (intercept) {
+                        mCancelMethod = ON_TOUCH_EVENT;
+                    } else {
+                        mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
+                    }
+            }
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            mInTouchEvent = false;
+        }
+
+        return intercept;
+    }
+
+    /**
+     * Set the width of each drag scroll region by specifying
+     * a fraction of the ListView height.
+     *
+     * @param heightFraction Fraction of ListView height. Capped at
+     * 0.5f.
+     * 
+     */
+    public void setDragScrollStart(float heightFraction) {
+        setDragScrollStarts(heightFraction, heightFraction);
+    }
+
+    /**
+     * Set the width of each drag scroll region by specifying
+     * a fraction of the ListView height.
+     *
+     * @param upperFrac Fraction of ListView height for up-scroll bound.
+     * Capped at 0.5f.
+     * @param lowerFrac Fraction of ListView height for down-scroll bound.
+     * Capped at 0.5f.
+     * 
+     */
+    public void setDragScrollStarts(float upperFrac, float lowerFrac) {
+        if (lowerFrac > 0.5f) {
+            mDragDownScrollStartFrac = 0.5f;
+        } else {
+            mDragDownScrollStartFrac = lowerFrac;
+        }
+
+        if (upperFrac > 0.5f) {
+            mDragUpScrollStartFrac = 0.5f;
+        } else {
+            mDragUpScrollStartFrac = upperFrac;
+        }
+
+        if (getHeight() != 0) {
+            updateScrollStarts();
+        }
+    }
+
+    private void continueDrag(int x, int y) {
+
+        // proposed position
+        mFloatLoc.x = x - mDragDeltaX;
+        mFloatLoc.y = y - mDragDeltaY;
+
+        doDragFloatView(true);
+
+        int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
+        int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
+
+        // get the current scroll direction
+        int currentScrollDir = mDragScroller.getScrollDir();
+
+        if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
+            // dragged down, it is below the down scroll start and it is not
+            // scrolling up
+
+            if (currentScrollDir != DragScroller.STOP) {
+                // moved directly from up scroll to down scroll
+                mDragScroller.stopScrolling(true);
+            }
+
+            // start scrolling down
+            mDragScroller.startScrolling(DragScroller.DOWN);
+        } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
+            // dragged up, it is above the up scroll start and it is not
+            // scrolling up
+
+            if (currentScrollDir != DragScroller.STOP) {
+                // moved directly from down scroll to up scroll
+                mDragScroller.stopScrolling(true);
+            }
+
+            // start scrolling up
+            mDragScroller.startScrolling(DragScroller.UP);
+        }
+        else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
+                && mDragScroller.isScrolling()) {
+            // not in the upper nor in the lower drag-scroll regions but it is
+            // still scrolling
+
+            mDragScroller.stopScrolling(true);
+        }
+    }
+
+    private void updateScrollStarts() {
+        final int padTop = getPaddingTop();
+        final int listHeight = getHeight() - padTop - getPaddingBottom();
+        float heightF = (float) listHeight;
+
+        mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
+        mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;
+
+        mUpScrollStartY = (int) mUpScrollStartYF;
+        mDownScrollStartY = (int) mDownScrollStartYF;
+
+        mDragUpScrollHeight = mUpScrollStartYF - padTop;
+        mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        updateScrollStarts();
+    }
+
+    private void adjustAllItems() {
+        final int first = getFirstVisiblePosition();
+        final int last = getLastVisiblePosition();
+
+        int begin = Math.max(0, getHeaderViewsCount() - first);
+        int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
+
+        for (int i = begin; i <= end; ++i) {
+            View v = getChildAt(i);
+            if (v != null) {
+                adjustItem(first + i, v, false);
+            }
+        }
+    }
+
+    private void adjustItem(int position) {
+        View v = getChildAt(position - getFirstVisiblePosition());
+
+        if (v != null) {
+            adjustItem(position, v, false);
+        }
+    }
+
+    /**
+     * Sets layout param height, gravity, and visibility  on
+     * wrapped item.
+     */
+    private void adjustItem(int position, View v, boolean invalidChildHeight) {
+
+        // Adjust item height
+        ViewGroup.LayoutParams lp = v.getLayoutParams();
+        int height;
+        if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) {
+            height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        } else {
+            height = calcItemHeight(position, v, invalidChildHeight);
+        }
+
+        if (height != lp.height) {
+            lp.height = height;
+            v.setLayoutParams(lp);
+        }
+
+        // Adjust item gravity
+        if (position == mFirstExpPos || position == mSecondExpPos) {
+            if (position < mSrcPos) {
+                ((DragSortItemView) v).setGravity(Gravity.BOTTOM);
+            } else if (position > mSrcPos) {
+                ((DragSortItemView) v).setGravity(Gravity.TOP);
+            }
+        }
+
+        // Finally adjust item visibility
+
+        int oldVis = v.getVisibility();
+        int vis = View.VISIBLE;
+
+        if (position == mSrcPos && mFloatView != null) {
+            vis = View.INVISIBLE;
+        }
+
+        if (vis != oldVis) {
+            v.setVisibility(vis);
+        }
+    }
+
+    private int getChildHeight(int position) {
+        if (position == mSrcPos) {
+            return 0;
+        }
+
+        View v = getChildAt(position - getFirstVisiblePosition());
+
+        if (v != null) {
+            // item is onscreen, therefore child height is valid,
+            // hence the "true"
+            return getChildHeight(position, v, false);
+        } else {
+            // item is offscreen
+            // first check cache for child height at this position
+            int childHeight = mChildHeightCache.get(position);
+            if (childHeight != -1) {
+                // Log.d("mobeta", "found child height in cache!");
+                return childHeight;
+            }
+
+            final ListAdapter adapter = getAdapter();
+            int type = adapter.getItemViewType(position);
+
+            // There might be a better place for checking for the following
+            final int typeCount = adapter.getViewTypeCount();
+            if (typeCount != mSampleViewTypes.length) {
+                mSampleViewTypes = new View[typeCount];
+            }
+
+            if (type >= 0) {
+                if (mSampleViewTypes[type] == null) {
+                    v = adapter.getView(position, null, this);
+                    mSampleViewTypes[type] = v;
+                } else {
+                    v = adapter.getView(position, mSampleViewTypes[type], this);
+                }
+            } else {
+                // type is HEADER_OR_FOOTER or IGNORE
+                v = adapter.getView(position, null, this);
+            }
+
+            // current child height is invalid, hence "true" below
+            childHeight = getChildHeight(position, v, true);
+
+            // cache it because this could have been expensive
+            mChildHeightCache.add(position, childHeight);
+
+            return childHeight;
+        }
+    }
+
+    private int getChildHeight(int position, View item, boolean invalidChildHeight) {
+        if (position == mSrcPos) {
+            return 0;
+        }
+
+        View child;
+        if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) {
+            child = item;
+        } else {
+            child = ((ViewGroup) item).getChildAt(0);
+        }
+
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+        if (lp != null) {
+            if (lp.height > 0) {
+                return lp.height;
+            }
+        }
+
+        int childHeight = child.getHeight();
+
+        if (childHeight == 0 || invalidChildHeight) {
+            measureItem(child);
+            childHeight = child.getMeasuredHeight();
+        }
+
+        return childHeight;
+    }
+
+    private int calcItemHeight(int position, View item, boolean invalidChildHeight) {
+        return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight));
+    }
+
+    private int calcItemHeight(int position, int childHeight) {
+
+        int divHeight = getDividerHeight();
+
+        boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
+        int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+        int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight);
+
+        int height;
+
+        if (position == mSrcPos) {
+            if (mSrcPos == mFirstExpPos) {
+                if (isSliding) {
+                    height = slideHeight + mItemHeightCollapsed;
+                } else {
+                    height = mFloatViewHeight;
+                }
+            } else if (mSrcPos == mSecondExpPos) {
+                // if gets here, we know an item is sliding
+                height = mFloatViewHeight - slideHeight;
+            } else {
+                height = mItemHeightCollapsed;
+            }
+        } else if (position == mFirstExpPos) {
+            if (isSliding) {
+                height = childHeight + slideHeight;
+            } else {
+                height = childHeight + maxNonSrcBlankHeight;
+            }
+        } else if (position == mSecondExpPos) {
+            // we know an item is sliding (b/c 2ndPos != 1stPos)
+            height = childHeight + maxNonSrcBlankHeight - slideHeight;
+        } else {
+            height = childHeight;
+        }
+
+        return height;
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests) {
+            super.requestLayout();
+        }
+    }
+
+    private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) {
+        int adjust = 0;
+
+        final int childHeight = getChildHeight(movePos);
+
+        int moveHeightBefore = moveItem.getHeight();
+        int moveHeightAfter = calcItemHeight(movePos, childHeight);
+
+        int moveBlankBefore = moveHeightBefore;
+        int moveBlankAfter = moveHeightAfter;
+        if (movePos != mSrcPos) {
+            moveBlankBefore -= childHeight;
+            moveBlankAfter -= childHeight;
+        }
+
+        int maxBlank = mFloatViewHeight;
+        if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) {
+            maxBlank -= mItemHeightCollapsed;
+        }
+
+        if (movePos <= oldFirstExpPos) {
+            if (movePos > mFirstExpPos) {
+                adjust += maxBlank - moveBlankAfter;
+            }
+        } else if (movePos == oldSecondExpPos) {
+            if (movePos <= mFirstExpPos) {
+                adjust += moveBlankBefore - maxBlank;
+            } else if (movePos == mSecondExpPos) {
+                adjust += moveHeightBefore - moveHeightAfter;
+            } else {
+                adjust += moveBlankBefore;
+            }
+        } else {
+            if (movePos <= mFirstExpPos) {
+                adjust -= maxBlank;
+            } else if (movePos == mSecondExpPos) {
+                adjust -= moveBlankAfter;
+            }
+        }
+
+        return adjust;
+    }
+
+    private void measureItem(View item) {
+        ViewGroup.LayoutParams lp = item.getLayoutParams();
+        if (lp == null) {
+            lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            item.setLayoutParams(lp);
+        }
+        int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
+                + getListPaddingRight(), lp.width);
+        int hspec;
+        if (lp.height > 0) {
+            hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+        } else {
+            hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        }
+        item.measure(wspec, hspec);
+    }
+
+    private void measureFloatView() {
+        if (mFloatView != null) {
+            measureItem(mFloatView);
+            mFloatViewHeight = mFloatView.getMeasuredHeight();
+            mFloatViewHeightHalf = mFloatViewHeight / 2;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        // Log.d("mobeta", "onMeasure called");
+        if (mFloatView != null) {
+            if (mFloatView.isLayoutRequested()) {
+                measureFloatView();
+            }
+            mFloatViewOnMeasured = true; // set to false after layout
+        }
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    @Override
+    protected void layoutChildren() {
+        super.layoutChildren();
+
+        if (mFloatView != null) {
+            if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) {
+                // Have to measure here when usual android measure
+                // pass is skipped. This happens during a drag-sort
+                // when layoutChildren is called directly.
+                measureFloatView();
+            }
+            mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
+            mFloatViewOnMeasured = false;
+        }
+    }
+
+    protected boolean onDragTouchEvent(MotionEvent ev) {
+        // we are in a drag
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_CANCEL:
+                if (mDragState == DRAGGING) {
+                    cancelDrag();
+                }
+                doActionUpOrCancel();
+                break;
+            case MotionEvent.ACTION_UP:
+                // Log.d("mobeta", "calling stopDrag from onDragTouchEvent");
+                if (mDragState == DRAGGING) {
+                    stopDrag(false);
+                }
+                doActionUpOrCancel();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                continueDrag((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+
+        return true;
+    }
+
+    private boolean mFloatViewInvalidated = false;
+
+    private void invalidateFloatView() {
+        mFloatViewInvalidated = true;
+    }
+
+    /**
+     * Start a drag of item at <code>position</code> using the
+     * registered FloatViewManager. Calls through
+     * to {@link #startDrag(int,View,int,int,int)} after obtaining
+     * the floating View from the FloatViewManager.
+     *
+     * @param position Item to drag.
+     * @param dragFlags Flags that restrict some movements of the
+     * floating View. For example, set <code>dragFlags |= 
+     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+     * View in all directions except off the screen to the left.
+     * @param deltaX Offset in x of the touch coordinate from the
+     * left edge of the floating View (i.e. touch-x minus float View
+     * left).
+     * @param deltaY Offset in y of the touch coordinate from the
+     * top edge of the floating View (i.e. touch-y minus float View
+     * top).
+     *
+     * @return True if the drag was started, false otherwise. This
+     * <code>startDrag</code> will fail if we are not currently in
+     * a touch event, there is no registered FloatViewManager,
+     * or the FloatViewManager returns a null View.
+     */
+    public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) {
+        if (!mInTouchEvent || mFloatViewManager == null) {
+            return false;
+        }
+
+        View v = mFloatViewManager.onCreateFloatView(position);
+
+        if (v == null) {
+            return false;
+        } else {
+            return startDrag(position, v, dragFlags, deltaX, deltaY);
+        }
+
+    }
+
+    /**
+     * Start a drag of item at <code>position</code> without using
+     * a FloatViewManager.
+     *
+     * @param position Item to drag.
+     * @param floatView Floating View.
+     * @param dragFlags Flags that restrict some movements of the
+     * floating View. For example, set <code>dragFlags |= 
+     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+     * View in all directions except off the screen to the left.
+     * @param deltaX Offset in x of the touch coordinate from the
+     * left edge of the floating View (i.e. touch-x minus float View
+     * left).
+     * @param deltaY Offset in y of the touch coordinate from the
+     * top edge of the floating View (i.e. touch-y minus float View
+     * top).
+     *
+     * @return True if the drag was started, false otherwise. This
+     * <code>startDrag</code> will fail if we are not currently in
+     * a touch event, <code>floatView</code> is null, or there is
+     * a drag in progress.
+     */
+    public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) {
+        if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null
+                || !mDragEnabled) {
+            return false;
+        }
+
+        if (getParent() != null) {
+            getParent().requestDisallowInterceptTouchEvent(true);
+        }
+
+        int pos = position + getHeaderViewsCount();
+        mFirstExpPos = pos;
+        mSecondExpPos = pos;
+        mSrcPos = pos;
+        mFloatPos = pos;
+
+        // mDragState = dragType;
+        mDragState = DRAGGING;
+        mDragFlags = 0;
+        mDragFlags |= dragFlags;
+
+        mFloatView = floatView;
+        measureFloatView(); // sets mFloatViewHeight
+
+        mDragDeltaX = deltaX;
+        mDragDeltaY = deltaY;
+        mDragStartY = mY;
+
+        // updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
+        mFloatLoc.x = mX - mDragDeltaX;
+        mFloatLoc.y = mY - mDragDeltaY;
+
+        // set src item invisible
+        final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
+
+        if (srcItem != null) {
+            srcItem.setVisibility(View.INVISIBLE);
+        }
+
+        if (mTrackDragSort) {
+            mDragSortTracker.startTracking();
+        }
+
+        // once float view is created, events are no longer passed
+        // to ListView
+        switch (mCancelMethod) {
+            case ON_TOUCH_EVENT:
+                super.onTouchEvent(mCancelEvent);
+                break;
+            case ON_INTERCEPT_TOUCH_EVENT:
+                super.onInterceptTouchEvent(mCancelEvent);
+                break;
+        }
+
+        requestLayout();
+
+        if (mLiftAnimator != null) {
+            mLiftAnimator.start();
+        }
+
+        return true;
+    }
+
+    private void doDragFloatView(boolean forceInvalidate) {
+        int movePos = getFirstVisiblePosition() + getChildCount() / 2;
+        View moveItem = getChildAt(getChildCount() / 2);
+
+        if (moveItem == null) {
+            return;
+        }
+
+        doDragFloatView(movePos, moveItem, forceInvalidate);
+    }
+
+    private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) {
+        mBlockLayoutRequests = true;
+
+        updateFloatView();
+
+        int oldFirstExpPos = mFirstExpPos;
+        int oldSecondExpPos = mSecondExpPos;
+
+        boolean updated = updatePositions();
+
+        if (updated) {
+            adjustAllItems();
+            int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos);
+            // Log.d("mobeta", "  adjust scroll="+scroll);
+
+            setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop());
+            layoutChildren();
+        }
+
+        if (updated || forceInvalidate) {
+            invalidate();
+        }
+
+        mBlockLayoutRequests = false;
+    }
+
+    /**
+     * Sets float View location based on suggested values and
+     * constraints set in mDragFlags.
+     */
+    private void updateFloatView() {
+
+        if (mFloatViewManager != null) {
+            mTouchLoc.set(mX, mY);
+            mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc);
+        }
+
+        final int floatX = mFloatLoc.x;
+        final int floatY = mFloatLoc.y;
+
+        // restrict x motion
+        int padLeft = getPaddingLeft();
+        if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
+            mFloatLoc.x = padLeft;
+        } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
+            mFloatLoc.x = padLeft;
+        }
+
+        // keep floating view from going past bottom of last header view
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+        final int firstPos = getFirstVisiblePosition();
+        final int lastPos = getLastVisiblePosition();
+
+        // Log.d("mobeta",
+        // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
+        int topLimit = getPaddingTop();
+        if (firstPos < numHeaders) {
+            topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
+        }
+        if ((mDragFlags & DRAG_NEG_Y) == 0) {
+            if (firstPos <= mSrcPos) {
+                topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
+            }
+        }
+        // bottom limit is top of first footer View or
+        // bottom of last item in list
+        int bottomLimit = getHeight() - getPaddingBottom();
+        if (lastPos >= getCount() - numFooters - 1) {
+            bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
+        }
+        if ((mDragFlags & DRAG_POS_Y) == 0) {
+            if (lastPos >= mSrcPos) {
+                bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
+            }
+        }
+
+        // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
+        // Log.d("mobeta", "limit=" + limit);
+        // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
+
+        if (floatY < topLimit) {
+            mFloatLoc.y = topLimit;
+        } else if (floatY + mFloatViewHeight > bottomLimit) {
+            mFloatLoc.y = bottomLimit - mFloatViewHeight;
+        }
+
+        // get y-midpoint of floating view (constrained to ListView bounds)
+        mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf;
+    }
+
+    private void destroyFloatView() {
+        if (mFloatView != null) {
+            mFloatView.setVisibility(GONE);
+            if (mFloatViewManager != null) {
+                mFloatViewManager.onDestroyFloatView(mFloatView);
+            }
+            mFloatView = null;
+            invalidate();
+        }
+    }
+
+    /**
+     * Interface for customization of the floating View appearance
+     * and dragging behavior. Implement
+     * your own and pass it to {@link #setFloatViewManager}. If
+     * your own is not passed, the default {@link SimpleFloatViewManager}
+     * implementation is used.
+     */
+    public interface FloatViewManager {
+        /**
+         * Return the floating View for item at <code>position</code>.
+         * DragSortListView will measure and layout this View for you,
+         * so feel free to just inflate it. You can help DSLV by
+         * setting some {@link ViewGroup.LayoutParams} on this View;
+         * otherwise it will set some for you (with a width of FILL_PARENT
+         * and a height of WRAP_CONTENT).
+         *
+         * @param position Position of item to drag (NOTE:
+         * <code>position</code> excludes header Views; thus, if you
+         * want to call {@link ListView#getChildAt(int)}, you will need
+         * to add {@link ListView#getHeaderViewsCount()} to the index).
+         *
+         * @return The View you wish to display as the floating View.
+         */
+        public View onCreateFloatView(int position);
+
+        /**
+         * Called whenever the floating View is dragged. Float View
+         * properties can be changed here. Also, the upcoming location
+         * of the float View can be altered by setting
+         * <code>location.x</code> and <code>location.y</code>.
+         *
+         * @param floatView The floating View.
+         * @param location The location (top-left; relative to DSLV
+         * top-left) at which the float
+         * View would like to appear, given the current touch location
+         * and the offset provided in {@link DragSortListView#startDrag}.
+         * @param touch The current touch location (relative to DSLV
+         * top-left).
+         * @param pendingScroll 
+         */
+        public void onDragFloatView(View floatView, Point location, Point touch);
+
+        /**
+         * Called when the float View is dropped; lets you perform
+         * any necessary cleanup. The internal DSLV floating View
+         * reference is set to null immediately after this is called.
+         *
+         * @param floatView The floating View passed to
+         * {@link #onCreateFloatView(int)}.
+         */
+        public void onDestroyFloatView(View floatView);
+    }
+
+    public void setFloatViewManager(FloatViewManager manager) {
+        mFloatViewManager = manager;
+    }
+
+    public void setDragListener(DragListener l) {
+        mDragListener = l;
+    }
+
+    /**
+     * Allows for easy toggling between a DragSortListView
+     * and a regular old ListView. If enabled, items are
+     * draggable, where the drag init mode determines how
+     * items are lifted (see {@link setDragInitMode(int)}).
+     * If disabled, items cannot be dragged.
+     *
+     * @param enabled Set <code>true</code> to enable list
+     * item dragging
+     */
+    public void setDragEnabled(boolean enabled) {
+        mDragEnabled = enabled;
+    }
+
+    public boolean isDragEnabled() {
+        return mDragEnabled;
+    }
+
+    /**
+     * This better reorder your ListAdapter! DragSortListView does not do this
+     * for you; doesn't make sense to. Make sure
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
+     * in your implementation. Furthermore, if you have a choiceMode other than
+     * none and the ListAdapter does not return true for
+     * {@link ListAdapter#hasStableIds()}, you will need to call
+     * {@link #moveCheckState(int, int)} to move the check boxes along with the
+     * list items.
+     * 
+     * @param l
+     */
+    public void setDropListener(DropListener l) {
+        mDropListener = l;
+    }
+
+    /**
+     * Probably a no-brainer, but make sure that your remove listener
+     * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it.
+     * When an item removal occurs, DragSortListView
+     * relies on a redraw of all the items to recover invisible views
+     * and such. Strictly speaking, if you remove something, your dataset
+     * has changed...
+     * 
+     * @param l
+     */
+    public void setRemoveListener(RemoveListener l) {
+        mRemoveListener = l;
+    }
+
+    public interface DragListener {
+        public void drag(int from, int to);
+    }
+
+    /**
+     * Your implementation of this has to reorder your ListAdapter! 
+     * Make sure to call
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+     * in your implementation.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface DropListener {
+        public void drop(int from, int to);
+    }
+
+    /**
+     * Make sure to call
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+     * in your implementation.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface RemoveListener {
+        public void remove(int which);
+    }
+
+    public interface DragSortListener extends DropListener, DragListener, RemoveListener {
+    }
+
+    public void setDragSortListener(DragSortListener l) {
+        setDropListener(l);
+        setDragListener(l);
+        setRemoveListener(l);
+    }
+
+    /**
+     * Completely custom scroll speed profile. Default increases linearly
+     * with position and is constant in time. Create your own by implementing
+     * {@link DragSortListView.DragScrollProfile}.
+     * 
+     * @param ssp
+     */
+    public void setDragScrollProfile(DragScrollProfile ssp) {
+        if (ssp != null) {
+            mScrollProfile = ssp;
+        }
+    }
+
+    /**
+     * Use this to move the check state of an item from one position to another
+     * in a drop operation. If you have a choiceMode which is not none, this
+     * method must be called when the order of items changes in an underlying
+     * adapter which does not have stable IDs (see
+     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
+     * ListView has no way of knowing which items have moved where, and cannot
+     * update the check state accordingly.
+     * <p>
+     * A word of warning about a "feature" in Android that you may run into when
+     * dealing with movable list items: for an adapter that <em>does</em> have
+     * stable IDs, ListView will attempt to locate each item based on its ID and
+     * move the check state from the item's old position to the new position —
+     * which is all fine and good (and removes the need for calling this
+     * function), except for the half-baked approach. Apparently to save time in
+     * the naive algorithm used, ListView will only search for an ID in the
+     * close neighborhood of the old position. If the user moves an item too far
+     * (specifically, more than 20 rows away), ListView will give up and just
+     * force the item to be unchecked. So if there is a reasonable chance that
+     * the user will move items more than 20 rows away from the original
+     * position, you may wish to use an adapter with unstable IDs and call this
+     * method manually instead.
+     * 
+     * @param from
+     * @param to
+     */
+    public void moveCheckState(int from, int to) {
+        // This method runs in O(n log n) time (n being the number of list
+        // items). The bottleneck is the call to AbsListView.setItemChecked,
+        // which is O(log n) because of the binary search involved in calling
+        // SparseBooleanArray.put().
+        //
+        // To improve on the average time, we minimize the number of calls to
+        // setItemChecked by only calling it for items that actually have a
+        // changed state. This is achieved by building a list containing the
+        // start and end of the "runs" of checked items, and then moving the
+        // runs. Note that moving an item from A to B is essentially a rotation
+        // of the range of items in [A, B]. Let's say we have
+        // . . U V X Y Z . .
+        // and move U after Z. This is equivalent to a rotation one step to the
+        // left within the range you are moving across:
+        // . . V X Y Z U . .
+        //
+        // So, to perform the move we enumerate all the runs within the move
+        // range, then rotate each run one step to the left or right (depending
+        // on move direction). For example, in the list:
+        // X X . X X X . X
+        // we have two runs. One begins at the last item of the list and wraps
+        // around to the beginning, ending at position 1. The second begins at
+        // position 3 and ends at position 5. To rotate a run, regardless of
+        // length, we only need to set a check mark at one end of the run, and
+        // clear a check mark at the other end:
+        // X . X X X . X X
+        SparseBooleanArray cip = getCheckedItemPositions();
+        int rangeStart = from;
+        int rangeEnd = to;
+        if (to < from) {
+            rangeStart = to;
+            rangeEnd = from;
+        }
+        rangeEnd += 1;
+
+        int[] runStart = new int[cip.size()];
+        int[] runEnd = new int[cip.size()];
+        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
+        if (runCount == 1 && (runStart[0] == runEnd[0])) {
+            // Special case where all items are checked, we can never set any
+            // item to false like we do below.
+            return;
+        }
+
+        if (from < to) {
+            for (int i = 0; i != runCount; i++) {
+                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
+                setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
+            }
+
+        } else {
+            for (int i = 0; i != runCount; i++) {
+                setItemChecked(runStart[i], false);
+                setItemChecked(runEnd[i], true);
+            }
+        }
+    }
+
+    /**
+     * Use this when an item has been deleted, to move the check state of all
+     * following items up one step. If you have a choiceMode which is not none,
+     * this method must be called when the order of items changes in an
+     * underlying adapter which does not have stable IDs (see
+     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
+     * ListView has no way of knowing which items have moved where, and cannot
+     * update the check state accordingly.
+     * 
+     * See also further comments on {@link #moveCheckState(int, int)}.
+     * 
+     * @param position
+     */
+    public void removeCheckState(int position) {
+        SparseBooleanArray cip = getCheckedItemPositions();
+
+        if (cip.size() == 0)
+            return;
+        int[] runStart = new int[cip.size()];
+        int[] runEnd = new int[cip.size()];
+        int rangeStart = position;
+        int rangeEnd = cip.keyAt(cip.size() - 1) + 1;
+        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
+        for (int i = 0; i != runCount; i++) {
+            if (!(runStart[i] == position || (runEnd[i] < runStart[i] && runEnd[i] > position))) {
+                // Only set a new check mark in front of this run if it does
+                // not contain the deleted position. If it does, we only need
+                // to make it one check mark shorter at the end.
+                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
+            }
+            setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
+        }
+    }
+
+    private static int buildRunList(SparseBooleanArray cip, int rangeStart,
+            int rangeEnd, int[] runStart, int[] runEnd) {
+        int runCount = 0;
+
+        int i = findFirstSetIndex(cip, rangeStart, rangeEnd);
+        if (i == -1)
+            return 0;
+
+        int position = cip.keyAt(i);
+        int currentRunStart = position;
+        int currentRunEnd = currentRunStart + 1;
+        for (i++; i < cip.size() && (position = cip.keyAt(i)) < rangeEnd; i++) {
+            if (!cip.valueAt(i)) // not checked => not interesting
+                continue;
+            if (position == currentRunEnd) {
+                currentRunEnd++;
+            } else {
+                runStart[runCount] = currentRunStart;
+                runEnd[runCount] = currentRunEnd;
+                runCount++;
+                currentRunStart = position;
+                currentRunEnd = position + 1;
+            }
+        }
+
+        if (currentRunEnd == rangeEnd) {
+            // rangeStart and rangeEnd are equivalent positions so to be
+            // consistent we translate them to the same integer value. That way
+            // we can check whether a run covers the entire range by just
+            // checking if the start equals the end position.
+            currentRunEnd = rangeStart;
+        }
+        runStart[runCount] = currentRunStart;
+        runEnd[runCount] = currentRunEnd;
+        runCount++;
+
+        if (runCount > 1) {
+            if (runStart[0] == rangeStart && runEnd[runCount - 1] == rangeStart) {
+                // The last run ends at the end of the range, and the first run
+                // starts at the beginning of the range. So they are actually
+                // part of the same run, except they wrap around the end of the
+                // range. To avoid adjacent runs, we need to merge them.
+                runStart[0] = runStart[runCount - 1];
+                runCount--;
+            }
+        }
+        return runCount;
+    }
+
+    private static int rotate(int value, int offset, int lowerBound, int upperBound) {
+        int windowSize = upperBound - lowerBound;
+
+        value += offset;
+        if (value < lowerBound) {
+            value += windowSize;
+        } else if (value >= upperBound) {
+            value -= windowSize;
+        }
+        return value;
+    }
+
+    private static int findFirstSetIndex(SparseBooleanArray sba, int rangeStart, int rangeEnd) {
+        int size = sba.size();
+        int i = insertionIndexForKey(sba, rangeStart);
+        while (i < size && sba.keyAt(i) < rangeEnd && !sba.valueAt(i))
+            i++;
+        if (i == size || sba.keyAt(i) >= rangeEnd)
+            return -1;
+        return i;
+    }
+
+    private static int insertionIndexForKey(SparseBooleanArray sba, int key) {
+        int low = 0;
+        int high = sba.size();
+        while (high - low > 0) {
+            int middle = (low + high) >> 1;
+            if (sba.keyAt(middle) < key)
+                low = middle + 1;
+            else
+                high = middle;
+        }
+        return low;
+    }
+
+    /**
+     * Interface for controlling
+     * scroll speed as a function of touch position and time. Use
+     * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to
+     * set custom profile.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface DragScrollProfile {
+        /**
+         * Return a scroll speed in pixels/millisecond. Always return a
+         * positive number.
+         * 
+         * @param w Normalized position in scroll region (i.e. w \in [0,1]).
+         * Small w typically means slow scrolling.
+         * @param t Time (in milliseconds) since start of scroll (handy if you
+         * want scroll acceleration).
+         * @return Scroll speed at position w and time t in pixels/ms.
+         */
+        float getSpeed(float w, long t);
+    }
+
+    private class DragScroller implements Runnable {
+
+        private boolean mAbort;
+
+        private long mPrevTime;
+        private long mCurrTime;
+
+        private int dy;
+        private float dt;
+        private long tStart;
+        private int scrollDir;
+
+        public final static int STOP = -1;
+        public final static int UP = 0;
+        public final static int DOWN = 1;
+
+        private float mScrollSpeed; // pixels per ms
+
+        private boolean mScrolling = false;
+
+        private int mLastHeader;
+        private int mFirstFooter;
+
+        public boolean isScrolling() {
+            return mScrolling;
+        }
+
+        public int getScrollDir() {
+            return mScrolling ? scrollDir : STOP;
+        }
+
+        public DragScroller() {
+        }
+
+        public void startScrolling(int dir) {
+            if (!mScrolling) {
+                // Debug.startMethodTracing("dslv-scroll");
+                mAbort = false;
+                mScrolling = true;
+                tStart = SystemClock.uptimeMillis();
+                mPrevTime = tStart;
+                scrollDir = dir;
+                post(this);
+            }
+        }
+
+        public void stopScrolling(boolean now) {
+            if (now) {
+                DragSortListView.this.removeCallbacks(this);
+                mScrolling = false;
+            } else {
+                mAbort = true;
+            }
+
+            // Debug.stopMethodTracing();
+        }
+
+        @Override
+        public void run() {
+            if (mAbort) {
+                mScrolling = false;
+                return;
+            }
+
+            // Log.d("mobeta", "scroll");
+
+            final int first = getFirstVisiblePosition();
+            final int last = getLastVisiblePosition();
+            final int count = getCount();
+            final int padTop = getPaddingTop();
+            final int listHeight = getHeight() - padTop - getPaddingBottom();
+
+            int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
+            int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
+
+            if (scrollDir == UP) {
+                View v = getChildAt(0);
+                // Log.d("mobeta", "vtop="+v.getTop()+" padtop="+padTop);
+                if (v == null) {
+                    mScrolling = false;
+                    return;
+                } else {
+                    if (first == 0 && v.getTop() == padTop) {
+                        mScrolling = false;
+                        return;
+                    }
+                }
+                mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
+                        / mDragUpScrollHeight, mPrevTime);
+            } else {
+                View v = getChildAt(last - first);
+                if (v == null) {
+                    mScrolling = false;
+                    return;
+                } else {
+                    if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
+                        mScrolling = false;
+                        return;
+                    }
+                }
+                mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
+                        / mDragDownScrollHeight, mPrevTime);
+            }
+
+            mCurrTime = SystemClock.uptimeMillis();
+            dt = (float) (mCurrTime - mPrevTime);
+
+            // dy is change in View position of a list item; i.e. positive dy
+            // means user is scrolling up (list item moves down the screen,
+            // remember
+            // y=0 is at top of View).
+            dy = (int) Math.round(mScrollSpeed * dt);
+
+            int movePos;
+            if (dy >= 0) {
+                dy = Math.min(listHeight, dy);
+                movePos = first;
+            } else {
+                dy = Math.max(-listHeight, dy);
+                movePos = last;
+            }
+
+            final View moveItem = getChildAt(movePos - first);
+            int top = moveItem.getTop() + dy;
+
+            if (movePos == 0 && top > padTop) {
+                top = padTop;
+            }
+
+            // always do scroll
+            mBlockLayoutRequests = true;
+
+            setSelectionFromTop(movePos, top - padTop);
+            DragSortListView.this.layoutChildren();
+            invalidate();
+
+            mBlockLayoutRequests = false;
+
+            // scroll means relative float View movement
+            doDragFloatView(movePos, moveItem, false);
+
+            mPrevTime = mCurrTime;
+            // Log.d("mobeta", "  updated prevTime="+mPrevTime);
+
+            post(this);
+        }
+    }
+
+    private class DragSortTracker {
+        StringBuilder mBuilder = new StringBuilder();
+
+        File mFile;
+
+        private int mNumInBuffer = 0;
+        private int mNumFlushes = 0;
+
+        private boolean mTracking = false;
+
+        public DragSortTracker() {
+            File root = Environment.getExternalStorageDirectory();
+            mFile = new File(root, "dslv_state.txt");
+
+            if (!mFile.exists()) {
+                try {
+                    mFile.createNewFile();
+                    Log.d("mobeta", "file created");
+                } catch (IOException e) {
+                    Log.w("mobeta", "Could not create dslv_state.txt");
+                    Log.d("mobeta", e.getMessage());
+                }
+            }
+
+        }
+
+        public void startTracking() {
+            mBuilder.append("<DSLVStates>\n");
+            mNumFlushes = 0;
+            mTracking = true;
+        }
+
+        public void appendState() {
+            if (!mTracking) {
+                return;
+            }
+
+            mBuilder.append("<DSLVState>\n");
+            final int children = getChildCount();
+            final int first = getFirstVisiblePosition();
+            mBuilder.append("    <Positions>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(first + i).append(",");
+            }
+            mBuilder.append("</Positions>\n");
+
+            mBuilder.append("    <Tops>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getChildAt(i).getTop()).append(",");
+            }
+            mBuilder.append("</Tops>\n");
+            mBuilder.append("    <Bottoms>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getChildAt(i).getBottom()).append(",");
+            }
+            mBuilder.append("</Bottoms>\n");
+
+            mBuilder.append("    <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
+            mBuilder.append("    <FirstExpBlankHeight>")
+                    .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos))
+                    .append("</FirstExpBlankHeight>\n");
+            mBuilder.append("    <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
+            mBuilder.append("    <SecondExpBlankHeight>")
+                    .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos))
+                    .append("</SecondExpBlankHeight>\n");
+            mBuilder.append("    <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
+            mBuilder.append("    <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
+                    .append("</SrcHeight>\n");
+            mBuilder.append("    <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
+            mBuilder.append("    <LastY>").append(mLastY).append("</LastY>\n");
+            mBuilder.append("    <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
+            mBuilder.append("    <ShuffleEdges>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
+            }
+            mBuilder.append("</ShuffleEdges>\n");
+
+            mBuilder.append("</DSLVState>\n");
+            mNumInBuffer++;
+
+            if (mNumInBuffer > 1000) {
+                flush();
+                mNumInBuffer = 0;
+            }
+        }
+
+        public void flush() {
+            if (!mTracking) {
+                return;
+            }
+
+            // save to file on sdcard
+            try {
+                boolean append = true;
+                if (mNumFlushes == 0) {
+                    append = false;
+                }
+                FileWriter writer = new FileWriter(mFile, append);
+
+                writer.write(mBuilder.toString());
+                mBuilder.delete(0, mBuilder.length());
+
+                writer.flush();
+                writer.close();
+
+                mNumFlushes++;
+            } catch (IOException e) {
+                // do nothing
+            }
+        }
+
+        public void stopTracking() {
+            if (mTracking) {
+                mBuilder.append("</DSLVStates>\n");
+                flush();
+                mTracking = false;
+            }
+        }
+
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/ResourceDragSortCursorAdapter.java b/ring-android/src/cx/ring/views/dragsortlv/ResourceDragSortCursorAdapter.java
new file mode 100644
index 0000000..6bec6a2
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/ResourceDragSortCursorAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cx.ring.views.dragsortlv;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+// taken from v4 rev. 10 ResourceCursorAdapter.java
+
+/**
+ * Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
+ * Used to write apps that run on platforms prior to Android 3.0.  When running
+ * on Android 3.0 or above, this implementation is still used; it does not try
+ * to switch to the framework's implementation.  See the framework SDK
+ * documentation for a class overview.
+ */
+public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
+    private int mLayout;
+
+    private int mDropDownLayout;
+    
+    private LayoutInflater mInflater;
+    
+    /**
+     * Constructor the enables auto-requery.
+     *
+     * @deprecated This option is discouraged, as it results in Cursor queries
+     * being performed on the application's UI thread and thus can cause poor
+     * responsiveness or even Application Not Responding errors.  As an alternative,
+     * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     */
+    @Deprecated
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
+        super(context, c);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+    
+    /**
+     * Constructor with default behavior as per
+     * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
+     * you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
+     * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+     * will always be set.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param autoRequery If true the adapter will call requery() on the
+     *                    cursor whenever it changes so the most recent
+     *                    data is always displayed.  Using true here is discouraged.
+     */
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
+        super(context, c, autoRequery);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Standard constructor.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout Resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param flags Flags used to determine the behavior of the adapter,
+     * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
+     */
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
+        super(context, c, flags);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Inflates view(s) from the specified XML file.
+     * 
+     * @see android.widget.CursorAdapter#newView(android.content.Context,
+     *      android.database.Cursor, ViewGroup)
+     */
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mLayout, parent, false);
+    }
+
+    @Override
+    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mDropDownLayout, parent, false);
+    }
+
+    /**
+     * <p>Sets the layout resource of the item views.</p>
+     *
+     * @param layout the layout resources used to create item views
+     */
+    public void setViewResource(int layout) {
+        mLayout = layout;
+    }
+    
+    /**
+     * <p>Sets the layout resource of the drop down views.</p>
+     *
+     * @param dropDownLayout the layout resources used to create drop down views
+     */
+    public void setDropDownViewResource(int dropDownLayout) {
+        mDropDownLayout = dropDownLayout;
+    }
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/SimpleDragSortCursorAdapter.java b/ring-android/src/cx/ring/views/dragsortlv/SimpleDragSortCursorAdapter.java
new file mode 100644
index 0000000..df38a32
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/SimpleDragSortCursorAdapter.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cx.ring.views.dragsortlv;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.ImageView;
+
+// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
+
+/**
+ * An easy adapter to map columns from a cursor to TextViews or ImageViews
+ * defined in an XML file. You can specify which columns you want, which
+ * views you want to display the columns, and the XML file that defines
+ * the appearance of these views.
+ *
+ * Binding occurs in two phases. First, if a
+ * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+ * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+ * is invoked. If the returned value is true, binding has occured. If the
+ * returned value is false and the view to bind is a TextView,
+ * {@link #setViewText(TextView, String)} is invoked. If the returned value
+ * is false and the view to bind is an ImageView,
+ * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+ * binding can be found, an {@link IllegalStateException} is thrown.
+ *
+ * If this adapter is used with filtering, for instance in an
+ * {@link android.widget.AutoCompleteTextView}, you can use the
+ * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
+ * {@link android.widget.FilterQueryProvider} interfaces
+ * to get control over the filtering process. You can refer to
+ * {@link #convertToString(android.database.Cursor)} and
+ * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
+ */
+public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
+    /**
+     * A list of columns containing the data to bind to the UI.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mFrom;
+    /**
+     * A list of View ids representing the views to which the data must be bound.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mTo;
+
+    private int mStringConversionColumn = -1;
+    private CursorToStringConverter mCursorToStringConverter;
+    private ViewBinder mViewBinder;
+
+    String[] mOriginalFrom;
+
+    /**
+     * Constructor the enables auto-requery.
+     *
+     * @deprecated This option is discouraged, as it results in Cursor queries
+     * being performed on the application's UI thread and thus can cause poor
+     * responsiveness or even Application Not Responding errors.  As an alternative,
+     * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+     */
+    @Deprecated
+    public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
+        super(context, layout, c);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(c, from);
+    }
+
+    /**
+     * Standard constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item. The layout file should include at least
+     *            those named views defined in "to"
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     * @param flags Flags used to determine the behavior of the adapter,
+     * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
+     */
+    public SimpleDragSortCursorAdapter(Context context, int layout,
+            Cursor c, String[] from, int[] to, int flags) {
+        super(context, layout, c, flags);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(c, from);
+    }
+
+    /**
+     * Binds all of the field names passed into the "to" parameter of the
+     * constructor with their corresponding cursor columns as specified in the
+     * "from" parameter.
+     *
+     * Binding occurs in two phases. First, if a
+     * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+     * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+     * is invoked. If the returned value is true, binding has occured. If the
+     * returned value is false and the view to bind is a TextView,
+     * {@link #setViewText(TextView, String)} is invoked. If the returned value is
+     * false and the view to bind is an ImageView,
+     * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+     * binding can be found, an {@link IllegalStateException} is thrown.
+     *
+     * @throws IllegalStateException if binding cannot occur
+     * 
+     * @see android.widget.CursorAdapter#bindView(android.view.View,
+     *      android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     * @see #setViewImage(ImageView, String)
+     * @see #setViewText(TextView, String)
+     */
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        final ViewBinder binder = mViewBinder;
+        final int count = mTo.length;
+        final int[] from = mFrom;
+        final int[] to = mTo;
+
+        for (int i = 0; i < count; i++) {
+            final View v = view.findViewById(to[i]);
+            if (v != null) {
+                boolean bound = false;
+                if (binder != null) {
+                    bound = binder.setViewValue(v, cursor, from[i]);
+                }
+
+                if (!bound) {
+                    String text = cursor.getString(from[i]);
+                    if (text == null) {
+                        text = "";
+                    }
+
+                    if (v instanceof TextView) {
+                        setViewText((TextView) v, text);
+                    } else if (v instanceof ImageView) {
+                        setViewImage((ImageView) v, text);
+                    } else {
+                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
+                                " view that can be bounds by this SimpleCursorAdapter");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link ViewBinder} used to bind data to views.
+     *
+     * @return a ViewBinder or null if the binder does not exist
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     */
+    public ViewBinder getViewBinder() {
+        return mViewBinder;
+    }
+
+    /**
+     * Sets the binder used to bind data to views.
+     *
+     * @param viewBinder the binder used to bind data to views, can be null to
+     *        remove the existing binder
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     */
+    public void setViewBinder(ViewBinder viewBinder) {
+        mViewBinder = viewBinder;
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an ImageView.
+     *
+     * By default, the value will be treated as an image resource. If the
+     * value cannot be used as an image resource, the value is used as an
+     * image Uri.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     *
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the cursor
+     */
+    public void setViewImage(ImageView v, String value) {
+        try {
+            v.setImageResource(Integer.parseInt(value));
+        } catch (NumberFormatException nfe) {
+            v.setImageURI(Uri.parse(value));
+        }
+    }
+
+    /**
+     * Called by bindView() to set the text for a TextView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to a TextView.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     * 
+     * @param v TextView to receive text
+     * @param text the text to be set for the TextView
+     */    
+    public void setViewText(TextView v, String text) {
+        v.setText(text);
+    }
+
+    /**
+     * Return the index of the column used to get a String representation
+     * of the Cursor.
+     *
+     * @return a valid index in the current Cursor or -1
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #setStringConversionColumn(int) 
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public int getStringConversionColumn() {
+        return mStringConversionColumn;
+    }
+
+    /**
+     * Defines the index of the column in the Cursor used to get a String
+     * representation of that Cursor. The column is used to convert the
+     * Cursor to a String only when the current CursorToStringConverter
+     * is null.
+     *
+     * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
+     *        conversion mechanism
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #getStringConversionColumn()
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public void setStringConversionColumn(int stringConversionColumn) {
+        mStringConversionColumn = stringConversionColumn;
+    }
+
+    /**
+     * Returns the converter used to convert the filtering Cursor
+     * into a String.
+     *
+     * @return null if the converter does not exist or an instance of
+     *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public CursorToStringConverter getCursorToStringConverter() {
+        return mCursorToStringConverter;
+    }
+
+    /**
+     * Sets the converter  used to convert the filtering Cursor
+     * into a String.
+     *
+     * @param cursorToStringConverter the Cursor to String converter, or
+     *        null to remove the converter
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) 
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
+        mCursorToStringConverter = cursorToStringConverter;
+    }
+
+    /**
+     * Returns a CharSequence representation of the specified Cursor as defined
+     * by the current CursorToStringConverter. If no CursorToStringConverter
+     * has been set, the String conversion column is used instead. If the
+     * conversion column is -1, the returned String is empty if the cursor
+     * is null or Cursor.toString().
+     *
+     * @param cursor the Cursor to convert to a CharSequence
+     *
+     * @return a non-null CharSequence representing the cursor
+     */
+    @Override
+    public CharSequence convertToString(Cursor cursor) {
+        if (mCursorToStringConverter != null) {
+            return mCursorToStringConverter.convertToString(cursor);
+        } else if (mStringConversionColumn > -1) {
+            return cursor.getString(mStringConversionColumn);
+        }
+
+        return super.convertToString(cursor);
+    }
+
+    /**
+     * Create a map from an array of strings to an array of column-id integers in cursor c.
+     * If c is null, the array will be discarded.
+     *
+     * @param c the cursor to find the columns from
+     * @param from the Strings naming the columns of interest
+     */
+    private void findColumns(Cursor c, String[] from) {
+        if (c != null) {
+            int i;
+            int count = from.length;
+            if (mFrom == null || mFrom.length != count) {
+                mFrom = new int[count];
+            }
+            for (i = 0; i < count; i++) {
+                mFrom[i] = c.getColumnIndexOrThrow(from[i]);
+            }
+        } else {
+            mFrom = null;
+        }
+    }
+
+    @Override
+    public Cursor swapCursor(Cursor c) {
+        // super.swapCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        return super.swapCursor(c);
+    }
+    
+    /**
+     * Change the cursor and change the column-to-view mappings at the same time.
+     *  
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     */
+    public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
+        mOriginalFrom = from;
+        mTo = to;
+        // super.changeCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        super.changeCursor(c);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to bind values fom the Cursor to views.
+     *
+     * You should use this class to bind values from the Cursor to views
+     * that are not directly supported by SimpleCursorAdapter or to
+     * change the way binding occurs for views supported by
+     * SimpleCursorAdapter.
+     *
+     * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see SimpleCursorAdapter#setViewImage(ImageView, String) 
+     * @see SimpleCursorAdapter#setViewText(TextView, String)
+     */
+    public static interface ViewBinder {
+        /**
+         * Binds the Cursor column defined by the specified index to the specified view.
+         *
+         * When binding is handled by this ViewBinder, this method must return true.
+         * If this method returns false, SimpleCursorAdapter will attempts to handle
+         * the binding on its own.
+         *
+         * @param view the view to bind the data to
+         * @param cursor the cursor to get the data from
+         * @param columnIndex the column at which the data can be found in the cursor
+         *
+         * @return true if the data was bound to the view, false otherwise
+         */
+        boolean setViewValue(View view, Cursor cursor, int columnIndex);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to define how the Cursor should be converted to a String.
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public static interface CursorToStringConverter {
+        /**
+         * Returns a CharSequence representing the specified Cursor.
+         *
+         * @param cursor the cursor for which a CharSequence representation
+         *        is requested
+         *
+         * @return a non-null CharSequence representing the cursor
+         */
+        CharSequence convertToString(Cursor cursor);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/dragsortlv/SimpleFloatViewManager.java b/ring-android/src/cx/ring/views/dragsortlv/SimpleFloatViewManager.java
new file mode 100644
index 0000000..5059f59
--- /dev/null
+++ b/ring-android/src/cx/ring/views/dragsortlv/SimpleFloatViewManager.java
@@ -0,0 +1,88 @@
+package cx.ring.views.dragsortlv;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+/**
+ * Simple implementation of the FloatViewManager class. Uses list
+ * items as they appear in the ListView to create the floating View.
+ */
+public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
+
+    private Bitmap mFloatBitmap;
+
+    private ImageView mImageView;
+
+    private int mFloatBGColor = Color.BLACK;
+
+    private ListView mListView;
+
+    public SimpleFloatViewManager(ListView lv) {
+        mListView = lv;
+    }
+
+    public void setBackgroundColor(int color) {
+        mFloatBGColor = color;
+    }
+
+    /**
+     * This simple implementation creates a Bitmap copy of the
+     * list item currently shown at ListView <code>position</code>.
+     */
+    @Override
+    public View onCreateFloatView(int position) {
+        // Guaranteed that this will not be null? I think so. Nope, got
+        // a NullPointerException once...
+        View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
+
+        if (v == null) {
+            return null;
+        }
+
+        v.setPressed(false);
+
+        // Create a copy of the drawing cache so that it does not get
+        // recycled by the framework when the list tries to clean up memory
+        //v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
+        v.setDrawingCacheEnabled(true);
+        mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
+        v.setDrawingCacheEnabled(false);
+
+        if (mImageView == null) {
+            mImageView = new ImageView(mListView.getContext());
+        }
+        mImageView.setBackgroundColor(mFloatBGColor);
+        mImageView.setPadding(0, 0, 0, 0);
+        mImageView.setImageBitmap(mFloatBitmap);
+        mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
+
+        return mImageView;
+    }
+
+    /**
+     * This does nothing
+     */
+    @Override
+    public void onDragFloatView(View floatView, Point position, Point touch) {
+        // do nothing
+    }
+
+    /**
+     * Removes the Bitmap from the ImageView created in
+     * onCreateFloatView() and tells the system to recycle it.
+     */
+    @Override
+    public void onDestroyFloatView(View floatView) {
+        ((ImageView) floatView).setImageDrawable(null);
+
+        mFloatBitmap.recycle();
+        mFloatBitmap = null;
+    }
+
+}
+
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/AdapterWrapper.java b/ring-android/src/cx/ring/views/stickylistheaders/AdapterWrapper.java
new file mode 100644
index 0000000..22b02a8
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/AdapterWrapper.java
@@ -0,0 +1,225 @@
+package cx.ring.views.stickylistheaders;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Checkable;
+import android.widget.ListAdapter;
+
+/**
+ * A {@link ListAdapter} which wraps a {@link StickyListHeadersAdapter} and
+ * automatically handles wrapping the result of
+ * {@link StickyListHeadersAdapter#getView(int, android.view.View, android.view.ViewGroup)}
+ * and
+ * {@link StickyListHeadersAdapter#getHeaderView(int, android.view.View, android.view.ViewGroup)}
+ * appropriately.
+ *
+ * @author Jake Wharton (jakewharton@gmail.com)
+ */
+class AdapterWrapper extends BaseAdapter implements StickyListHeadersAdapter {
+
+	interface OnHeaderClickListener {
+		public void onHeaderClick(View header, int itemPosition, long headerId);
+	}
+
+	final StickyListHeadersAdapter mDelegate;
+	private final List<View> mHeaderCache = new LinkedList<View>();
+	private final Context mContext;
+	private Drawable mDivider;
+	private int mDividerHeight;
+	private OnHeaderClickListener mOnHeaderClickListener;
+	private DataSetObserver mDataSetObserver = new DataSetObserver() {
+
+		@Override
+		public void onInvalidated() {
+			mHeaderCache.clear();
+			AdapterWrapper.super.notifyDataSetInvalidated();
+		}
+		
+		@Override
+		public void onChanged() {
+			AdapterWrapper.super.notifyDataSetChanged();
+		}
+	};
+
+	AdapterWrapper(Context context,
+			StickyListHeadersAdapter delegate) {
+		this.mContext = context;
+		this.mDelegate = delegate;
+		delegate.registerDataSetObserver(mDataSetObserver);
+	}
+
+	void setDivider(Drawable divider, int dividerHeight) {
+		this.mDivider = divider;
+		this.mDividerHeight = dividerHeight;
+		notifyDataSetChanged();
+	}
+
+	@Override
+	public boolean areAllItemsEnabled() {
+		return mDelegate.areAllItemsEnabled();
+	}
+
+	@Override
+	public boolean isEnabled(int position) {
+		return mDelegate.isEnabled(position);
+	}
+
+	@Override
+	public int getCount() {
+		return mDelegate.getCount();
+	}
+
+	@Override
+	public Object getItem(int position) {
+		return mDelegate.getItem(position);
+	}
+
+	@Override
+	public long getItemId(int position) {
+		return mDelegate.getItemId(position);
+	}
+
+	@Override
+	public boolean hasStableIds() {
+		return mDelegate.hasStableIds();
+	}
+
+	@Override
+	public int getItemViewType(int position) {
+		return mDelegate.getItemViewType(position);
+	}
+
+	@Override
+	public int getViewTypeCount() {
+		return mDelegate.getViewTypeCount();
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return mDelegate.isEmpty();
+	}
+
+	/**
+	 * Will recycle header from {@link WrapperView} if it exists
+	 */
+	private void recycleHeaderIfExists(WrapperView wv) {
+		View header = wv.mHeader;
+		if (header != null) {
+			// reset the headers visibility when adding it to the cache
+			header.setVisibility(View.VISIBLE);
+			mHeaderCache.add(header);
+		}
+	}
+
+	/**
+	 * Get a header view. This optionally pulls a header from the supplied
+	 * {@link WrapperView} and will also recycle the divider if it exists.
+	 */
+	private View configureHeader(WrapperView wv, final int position) {
+		View header = wv.mHeader == null ? popHeader() : wv.mHeader;
+		header = mDelegate.getHeaderView(position, header, wv);
+		if (header == null) {
+			throw new NullPointerException("Header view must not be null.");
+		}
+		//if the header isn't clickable, the listselector will be drawn on top of the header
+		header.setClickable(true);
+		header.setOnClickListener(new OnClickListener() {
+
+			@Override
+			public void onClick(View v) {
+				if(mOnHeaderClickListener != null){
+					long headerId = mDelegate.getHeaderId(position);
+					mOnHeaderClickListener.onHeaderClick(v, position, headerId);
+				}
+			}
+		});
+		return header;
+	}
+
+	private View popHeader() {
+		if(mHeaderCache.size() > 0) {
+			return mHeaderCache.remove(0);
+		}
+		return null;
+	}
+
+	/** Returns {@code true} if the previous position has the same header ID. */
+	private boolean previousPositionHasSameHeader(int position) {
+		return position != 0
+				&& mDelegate.getHeaderId(position) == mDelegate
+						.getHeaderId(position - 1);
+	}
+
+	@Override
+	public WrapperView getView(int position, View convertView, ViewGroup parent) {
+		WrapperView wv = (convertView == null) ? new WrapperView(mContext) : (WrapperView) convertView;
+		View item = mDelegate.getView(position, wv.mItem, parent);
+		View header = null;
+		if (previousPositionHasSameHeader(position)) {
+			recycleHeaderIfExists(wv);
+		} else {
+			header = configureHeader(wv, position);
+		}
+		if((item instanceof Checkable) && !(wv instanceof CheckableWrapperView)) {
+			// Need to create Checkable subclass of WrapperView for ListView to work correctly
+			wv = new CheckableWrapperView(mContext);
+		} else if(!(item instanceof Checkable) && (wv instanceof CheckableWrapperView)) {
+			wv = new WrapperView(mContext);
+		}
+		wv.update(item, header, mDivider, mDividerHeight);
+		return wv;
+	}
+
+	public void setOnHeaderClickListener(OnHeaderClickListener onHeaderClickListener){
+		this.mOnHeaderClickListener = onHeaderClickListener;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return mDelegate.equals(o); 
+	}
+
+	@Override
+	public View getDropDownView(int position, View convertView, ViewGroup parent) {
+		return ((BaseAdapter) mDelegate).getDropDownView(position, convertView, parent);
+	}
+
+	@Override
+	public int hashCode() {
+		return mDelegate.hashCode();
+	}
+
+	@Override
+	public void notifyDataSetChanged() {
+		((BaseAdapter) mDelegate).notifyDataSetChanged();
+	}
+
+	@Override
+	public void notifyDataSetInvalidated() {
+		((BaseAdapter) mDelegate).notifyDataSetInvalidated();
+	}
+
+	@Override
+	public String toString() {
+		return mDelegate.toString();
+	}
+
+	@Override
+	public View getHeaderView(int position, View convertView, ViewGroup parent) {
+		return mDelegate.getHeaderView(position, convertView, parent);
+	}
+
+	@Override
+	public long getHeaderId(int position) {
+		return mDelegate.getHeaderId(position);
+	}
+
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java b/ring-android/src/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java
new file mode 100644
index 0000000..075dfb4
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java
@@ -0,0 +1,11 @@
+package cx.ring.views.stickylistheaders;
+
+public class ApiLevelTooLowException extends RuntimeException {
+
+    private static final long serialVersionUID = -5480068364264456757L;
+
+    public ApiLevelTooLowException(int versionCode) {
+        super("Requires API level " + versionCode);
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/CheckableWrapperView.java b/ring-android/src/cx/ring/views/stickylistheaders/CheckableWrapperView.java
new file mode 100644
index 0000000..e22204f
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/CheckableWrapperView.java
@@ -0,0 +1,31 @@
+package cx.ring.views.stickylistheaders;
+
+import android.content.Context;
+import android.widget.Checkable;
+
+/**
+ * A WrapperView that implements the checkable interface
+ * 
+ * @author Emil Sjölander
+ */
+class CheckableWrapperView extends WrapperView implements Checkable {
+
+	public CheckableWrapperView(final Context context) {
+		super(context);
+	}
+
+	@Override
+	public boolean isChecked() {
+		return ((Checkable) mItem).isChecked();
+	}
+
+	@Override
+	public void setChecked(final boolean checked) {
+		((Checkable) mItem).setChecked(checked);
+	}
+
+	@Override
+	public void toggle() {
+		setChecked(!isChecked());
+	}
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java b/ring-android/src/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java
new file mode 100644
index 0000000..dd823ff
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java
@@ -0,0 +1,32 @@
+package cx.ring.views.stickylistheaders;
+
+import android.content.Context;
+import android.widget.SectionIndexer;
+
+class SectionIndexerAdapterWrapper extends
+		AdapterWrapper implements SectionIndexer {
+	
+	final SectionIndexer mSectionIndexerDelegate;
+
+	SectionIndexerAdapterWrapper(Context context,
+			StickyListHeadersAdapter delegate) {
+		super(context, delegate);
+		mSectionIndexerDelegate = (SectionIndexer) delegate;
+	}
+
+	@Override
+	public int getPositionForSection(int section) {
+		return mSectionIndexerDelegate.getPositionForSection(section);
+	}
+
+	@Override
+	public int getSectionForPosition(int position) {
+		return mSectionIndexerDelegate.getSectionForPosition(position);
+	}
+
+	@Override
+	public Object[] getSections() {
+		return mSectionIndexerDelegate.getSections();
+	}
+
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java b/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java
new file mode 100644
index 0000000..236338d
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java
@@ -0,0 +1,38 @@
+package cx.ring.views.stickylistheaders;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+
+public interface StickyListHeadersAdapter extends ListAdapter {
+	/**
+	 * Get a View that displays the header data at the specified position in the
+	 * set. You can either create a View manually or inflate it from an XML layout
+	 * file.
+	 *
+	 * @param position
+	 * The position of the item within the adapter's data set of the item whose
+	 * header view we want.
+	 * @param convertView
+	 * The old view to reuse, if possible. Note: You should check that this view is
+	 * non-null and of an appropriate type before using. If it is not possible to
+	 * convert this view to display the correct data, this method can create a new
+	 * view.
+	 * @param parent
+	 * The parent that this view will eventually be attached to.
+	 * @return
+	 * A View corresponding to the data at the specified position.
+	 */
+	View getHeaderView(int position, View convertView, ViewGroup parent);
+
+	/**
+	 * Get the header id associated with the specified position in the list.
+	 *
+	 * @param position
+	 * The position of the item within the adapter's data set whose header id we
+	 * want.
+	 * @return
+	 * The id of the header at the specified position.
+	 */
+	long getHeaderId(int position);
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersListView.java b/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersListView.java
new file mode 100644
index 0000000..72780e9
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/StickyListHeadersListView.java
@@ -0,0 +1,993 @@
+package cx.ring.views.stickylistheaders;
+
+import cx.ring.R;
+import cx.ring.adapters.ContactsAdapter;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+
+/**
+ * Even though this is a FrameLayout subclass we it is called a ListView. This
+ * is because of 2 reasons. 1. It acts like as ListView 2. It used to be a
+ * ListView subclass and i did not was to change to name causing compatibility
+ * errors.
+ * 
+ * @author Emil Sjölander
+ */
+public class StickyListHeadersListView extends FrameLayout {
+
+	public interface OnHeaderClickListener {
+		public void onHeaderClick(StickyListHeadersListView l, View header,
+				int itemPosition, long headerId, boolean currentlySticky);
+	}
+
+	/* --- Children --- */
+	private WrapperViewList mList;
+	private View mHeader;
+
+	/* --- Header state --- */
+	private Long mHeaderId;
+	// used to not have to call getHeaderId() all the time
+	private Integer mHeaderPosition;
+	private Integer mHeaderOffset;
+
+	/* --- Delegates --- */
+	private OnScrollListener mOnScrollListenerDelegate;
+
+	/* --- Settings --- */
+	private boolean mAreHeadersSticky = true;
+	private boolean mClippingToPadding = true;
+	private boolean mIsDrawingListUnderStickyHeader = true;
+	private int mPaddingLeft = 0;
+	private int mPaddingTop = 0;
+	private int mPaddingRight = 0;
+	private int mPaddingBottom = 0;
+
+	/* --- Other --- */
+	private AdapterWrapper mAdapter;
+	private OnHeaderClickListener mOnHeaderClickListener;
+	private Drawable mDivider;
+	private int mDividerHeight;
+	private AdapterWrapperDataSetObserver mDataSetObserver;
+
+	public StickyListHeadersListView(Context context) {
+		this(context, null);
+	}
+
+	public StickyListHeadersListView(Context context, AttributeSet attrs) {
+		this(context, attrs, 0);
+	}
+
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public StickyListHeadersListView(Context context, AttributeSet attrs,
+			int defStyle) {
+		super(context, attrs, defStyle);
+
+		// Initialize the list
+		mList = new WrapperViewList(context, attrs);
+		mDivider = mList.getDivider();
+		mDividerHeight = mList.getDividerHeight();
+
+		// null out divider, dividers are handled by adapter so they look good
+		// with headers
+		mList.setDivider(null);
+		mList.setDividerHeight(0);
+
+		mList.setLifeCycleListener(new WrapperViewListLifeCycleListener());
+		mList.setOnScrollListener(new WrapperListScrollListener());
+		addView(mList);
+
+		if (attrs != null) {
+			TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+					R.styleable.StickyListHeadersListView, 0, 0);
+
+			try {
+				// Android attributes
+				if (a.hasValue(R.styleable.StickyListHeadersListView_android_padding)) {
+					int padding = a
+							.getDimensionPixelSize(
+									R.styleable.StickyListHeadersListView_android_padding,
+									0);
+					mPaddingLeft = padding;
+					mPaddingTop = padding;
+					mPaddingRight = padding;
+					mPaddingBottom = padding;
+				} else {
+					mPaddingLeft = a
+							.getDimensionPixelSize(
+									R.styleable.StickyListHeadersListView_android_paddingLeft,
+									0);
+					mPaddingTop = a
+							.getDimensionPixelSize(
+									R.styleable.StickyListHeadersListView_android_paddingTop,
+									0);
+					mPaddingRight = a
+							.getDimensionPixelSize(
+									R.styleable.StickyListHeadersListView_android_paddingRight,
+									0);
+					mPaddingBottom = a
+							.getDimensionPixelSize(
+									R.styleable.StickyListHeadersListView_android_paddingBottom,
+									0);
+				}
+				setPadding(mPaddingLeft, mPaddingTop, mPaddingRight,
+						mPaddingBottom);
+
+				// Set clip to padding on the list and reset value to default on
+				// wrapper
+				mClippingToPadding = a
+						.getBoolean(
+								R.styleable.StickyListHeadersListView_android_clipToPadding,
+								true);
+				super.setClipToPadding(true);
+				mList.setClipToPadding(mClippingToPadding);
+
+				// ListView attributes
+				mList.setFadingEdgeLength(a
+						.getDimensionPixelSize(
+								R.styleable.StickyListHeadersListView_android_fadingEdgeLength,
+								mList.getVerticalFadingEdgeLength()));
+				final int fadingEdge = a
+						.getInt(R.styleable.StickyListHeadersListView_android_requiresFadingEdge,
+								0);
+				if (fadingEdge == 0x00001000) {
+					mList.setVerticalFadingEdgeEnabled(false);
+					mList.setHorizontalFadingEdgeEnabled(true);
+				} else if (fadingEdge == 0x00002000) {
+					mList.setVerticalFadingEdgeEnabled(true);
+					mList.setHorizontalFadingEdgeEnabled(false);
+				} else {
+					mList.setVerticalFadingEdgeEnabled(false);
+					mList.setHorizontalFadingEdgeEnabled(false);
+				}
+				mList.setCacheColorHint(a
+						.getColor(
+								R.styleable.StickyListHeadersListView_android_cacheColorHint,
+								mList.getCacheColorHint()));
+				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+					mList.setChoiceMode(a
+							.getInt(R.styleable.StickyListHeadersListView_android_choiceMode,
+									mList.getChoiceMode()));
+				}
+				mList.setDrawSelectorOnTop(a
+						.getBoolean(
+								R.styleable.StickyListHeadersListView_android_drawSelectorOnTop,
+								false));
+				mList.setFastScrollEnabled(a
+						.getBoolean(
+								R.styleable.StickyListHeadersListView_android_fastScrollEnabled,
+								mList.isFastScrollEnabled()));
+				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+					mList.setFastScrollAlwaysVisible(a
+							.getBoolean(
+									R.styleable.StickyListHeadersListView_android_fastScrollAlwaysVisible,
+									mList.isFastScrollAlwaysVisible()));
+				}
+				mList.setScrollBarStyle(a
+						.getInt(R.styleable.StickyListHeadersListView_android_scrollbarStyle,
+								0));
+				final Drawable selector = a
+						.getDrawable(R.styleable.StickyListHeadersListView_android_listSelector);
+				if (selector != null) {
+					mList.setSelector(selector);
+				}
+				mList.setScrollingCacheEnabled(a
+						.getBoolean(
+								R.styleable.StickyListHeadersListView_android_scrollingCache,
+								mList.isScrollingCacheEnabled()));
+				final Drawable divider = a
+						.getDrawable(R.styleable.StickyListHeadersListView_android_divider);
+				if (divider != null) {
+					mDivider = divider;
+				}
+				mDividerHeight = a
+						.getDimensionPixelSize(
+								R.styleable.StickyListHeadersListView_android_dividerHeight,
+								mDividerHeight);
+
+				// StickyListHeaders attributes
+				mAreHeadersSticky = a.getBoolean(
+						R.styleable.StickyListHeadersListView_hasStickyHeaders,
+						true);
+				mIsDrawingListUnderStickyHeader = a
+						.getBoolean(
+								R.styleable.StickyListHeadersListView_isDrawingListUnderStickyHeader,
+								true);
+            } finally {
+				a.recycle();
+			}
+		}
+
+        mList.setVerticalScrollBarEnabled(isVerticalScrollBarEnabled());
+        mList.setHorizontalScrollBarEnabled(isHorizontalScrollBarEnabled());
+    }
+
+	@Override
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+		measureHeader(mHeader);
+	}
+
+	private void ensureHeaderHasCorrectLayoutParams(View header) {
+		ViewGroup.LayoutParams lp = header.getLayoutParams();
+		if (lp == null) {
+			lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+		} else if (lp.height == LayoutParams.MATCH_PARENT) {
+			lp.height = LayoutParams.WRAP_CONTENT;
+		}
+		header.setLayoutParams(lp);
+	}
+
+	private void measureHeader(View header) {
+		if (header != null) {
+			final int width = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
+			final int parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+					width, MeasureSpec.EXACTLY);
+			final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0,
+					MeasureSpec.UNSPECIFIED);
+			measureChild(header, parentWidthMeasureSpec,
+					parentHeightMeasureSpec);
+		}
+	}
+
+	@Override
+	protected void onLayout(boolean changed, int left, int top, int right,
+			int bottom) {
+		mList.layout(0, 0, mList.getMeasuredWidth(), getHeight());
+		if (mHeader != null) {
+			MarginLayoutParams lp = (MarginLayoutParams) mHeader
+					.getLayoutParams();
+			int headerTop = lp.topMargin
+					+ (mClippingToPadding ? mPaddingTop : 0);
+			// The left parameter must for some reason be set to 0.
+			// I think it should be set to mPaddingLeft but apparently not
+			mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth()
+					+ mPaddingLeft, headerTop + mHeader.getMeasuredHeight());
+		}
+	}
+
+	@Override
+	protected void dispatchDraw(Canvas canvas) {
+		// Only draw the list here.
+		// The header should be drawn right after the lists children are drawn.
+		// This is done so that the header is above the list items
+		// but below the list decorators (scroll bars etc).
+		drawChild(canvas, mList, 0);
+	}
+
+	// Reset values tied the header. also remove header form layout
+	// This is called in response to the data set or the adapter changing
+	private void clearHeader() {
+		if (mHeader != null) {
+			removeView(mHeader);
+			mHeader = null;
+			mHeaderId = null;
+			mHeaderPosition = null;
+			mHeaderOffset = null;
+
+			// reset the top clipping length
+			mList.setTopClippingLength(0);
+			updateHeaderVisibilities();
+		}
+	}
+
+	private void updateOrClearHeader(int firstVisiblePosition) {
+		final int adapterCount = mAdapter == null ? 0 : mAdapter.getCount();
+		if (adapterCount == 0 || !mAreHeadersSticky) {
+			return;
+		}
+
+		final int headerViewCount = mList.getHeaderViewsCount();
+		final int realFirstVisibleItem = firstVisiblePosition - headerViewCount;
+
+		// It is not a mistake to call getFirstVisiblePosition() here.
+		// Most of the time getFixedFirstVisibleItem() should be called
+		// but that does not work great together with getChildAt()
+		final boolean doesListHaveChildren = mList.getChildCount() != 0;
+		final boolean isFirstViewBelowTop = doesListHaveChildren && mList.getFirstVisiblePosition() == 0
+				&& mList.getChildAt(0).getTop() > 0;
+		final boolean isFirstVisibleItemOutsideAdapterRange = realFirstVisibleItem > adapterCount - 1
+				|| realFirstVisibleItem < 0;
+		if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange
+				|| isFirstViewBelowTop) {
+			clearHeader();
+			return;
+		}
+
+		updateHeader(realFirstVisibleItem);
+	}
+
+	private void updateHeader(int firstVisiblePosition) {
+
+		// check if there is a new header should be sticky
+		if (mHeaderPosition == null || mHeaderPosition != firstVisiblePosition) {
+			mHeaderPosition = firstVisiblePosition;
+			final long headerId = mAdapter.getHeaderId(firstVisiblePosition);
+			if (mHeaderId == null || mHeaderId != headerId) {
+				mHeaderId = headerId;
+				final View header = mAdapter.getHeaderView(mHeaderPosition,
+						mHeader, this);
+				if (mHeader != header) {
+					if (header == null) {
+						throw new NullPointerException("header may not be null");
+					}
+					swapHeader(header);
+				}
+				
+				ensureHeaderHasCorrectLayoutParams(mHeader);
+				measureHeader(mHeader);
+
+				// Reset mHeaderOffset to null ensuring
+				// that it will be set on the header and
+				// not skipped for performance reasons.
+				mHeaderOffset = null;
+			}
+		}
+
+		int headerOffset = 0;
+
+		// Calculate new header offset
+		// Skip looking at the first view. it never matters because it always
+		// results in a headerOffset = 0
+		int headerBottom = mHeader.getMeasuredHeight()
+				+ (mClippingToPadding ? mPaddingTop : 0);
+		for (int i = 0; i < mList.getChildCount(); i++) {
+			final View child = mList.getChildAt(i);
+			final boolean doesChildHaveHeader = child instanceof WrapperView
+					&& ((WrapperView) child).hasHeader();
+			final boolean isChildFooter = mList.containsFooterView(child);
+			if (child.getTop() >= (mClippingToPadding ? mPaddingTop : 0)
+					&& (doesChildHaveHeader || isChildFooter)) {
+				headerOffset = Math.min(child.getTop() - headerBottom, 0);
+				break;
+			}
+		}
+
+		setHeaderOffet(headerOffset);
+
+		if (!mIsDrawingListUnderStickyHeader) {
+			mList.setTopClippingLength(mHeader.getMeasuredHeight()
+					+ mHeaderOffset);
+		}
+
+		updateHeaderVisibilities();
+	}
+
+	private void swapHeader(View newHeader) {
+		if (mHeader != null) {
+			removeView(mHeader);
+		}
+		mHeader = newHeader;
+		addView(mHeader);
+		mHeader.setOnClickListener(new OnClickListener() {
+
+			@Override
+			public void onClick(View v) {
+				if (mOnHeaderClickListener != null) {
+					mOnHeaderClickListener.onHeaderClick(
+							StickyListHeadersListView.this, mHeader,
+							mHeaderPosition, mHeaderId, true);
+				}
+			}
+
+		});
+	}
+
+	// hides the headers in the list under the sticky header.
+	// Makes sure the other ones are showing
+	private void updateHeaderVisibilities() {
+		int top;
+		if (mHeader != null) {
+			top = mHeader.getMeasuredHeight()
+					+ (mHeaderOffset != null ? mHeaderOffset : 0);
+		} else {
+			top = mClippingToPadding ? mPaddingTop : 0;
+		}
+		int childCount = mList.getChildCount();
+		for (int i = 0; i < childCount; i++) {
+			View child = mList.getChildAt(i);
+			if (child instanceof WrapperView) {
+				WrapperView wrapperViewChild = (WrapperView) child;
+				if (wrapperViewChild.hasHeader()) {
+					View childHeader = wrapperViewChild.mHeader;
+					if (wrapperViewChild.getTop() < top) {
+						if (childHeader.getVisibility() != View.INVISIBLE) {
+							childHeader.setVisibility(View.INVISIBLE);
+						}
+					} else {
+						if (childHeader.getVisibility() != View.VISIBLE) {
+							childHeader.setVisibility(View.VISIBLE);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Wrapper around setting the header offset in different ways depending on
+	// the API version
+	@SuppressLint("NewApi")
+	private void setHeaderOffet(int offset) {
+		if (mHeaderOffset == null || mHeaderOffset != offset) {
+			mHeaderOffset = offset;
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+				mHeader.setTranslationY(mHeaderOffset);
+			} else {
+				MarginLayoutParams params = (MarginLayoutParams) mHeader
+						.getLayoutParams();
+				params.topMargin = mHeaderOffset;
+				mHeader.setLayoutParams(params);
+			}
+		}
+	}
+
+	private class AdapterWrapperDataSetObserver extends DataSetObserver {
+
+		@Override
+		public void onChanged() {
+			clearHeader();
+		}
+
+		@Override
+		public void onInvalidated() {
+			clearHeader();
+		}
+
+	}
+
+	private class WrapperListScrollListener implements OnScrollListener {
+
+		@Override
+		public void onScroll(AbsListView view, int firstVisibleItem,
+				int visibleItemCount, int totalItemCount) {
+			if (mOnScrollListenerDelegate != null) {
+				mOnScrollListenerDelegate.onScroll(view, firstVisibleItem,
+						visibleItemCount, totalItemCount);
+			}
+			updateOrClearHeader(mList.getFixedFirstVisibleItem());
+		}
+
+		@Override
+		public void onScrollStateChanged(AbsListView view, int scrollState) {
+			if (mOnScrollListenerDelegate != null) {
+				mOnScrollListenerDelegate.onScrollStateChanged(view,
+						scrollState);
+			}
+		}
+
+	}
+
+	private class WrapperViewListLifeCycleListener implements WrapperViewList.LifeCycleListener {
+
+		@Override
+		public void onDispatchDrawOccurred(Canvas canvas) {
+			// onScroll is not called often at all before froyo
+			// therefor we need to update the header here as well.
+			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
+				updateOrClearHeader(mList.getFixedFirstVisibleItem());
+			}
+			if (mHeader != null) {
+				if (mClippingToPadding) {
+					canvas.save();
+					canvas.clipRect(0, mPaddingTop, getRight(), getBottom());
+					drawChild(canvas, mHeader, 0);
+					canvas.restore();
+				} else {
+					drawChild(canvas, mHeader, 0);
+				}
+			}
+		}
+
+	}
+
+	private class AdapterWrapperHeaderClickHandler implements
+			AdapterWrapper.OnHeaderClickListener {
+
+		@Override
+		public void onHeaderClick(View header, int itemPosition, long headerId) {
+			mOnHeaderClickListener.onHeaderClick(
+					StickyListHeadersListView.this, header, itemPosition,
+					headerId, false);
+		}
+
+	}
+
+	private boolean isStartOfSection(int position) {
+		return position == 0
+				|| mAdapter.getHeaderId(position) != mAdapter
+						.getHeaderId(position - 1);
+	}
+
+	private int getHeaderOverlap(int position) {
+		boolean isStartOfSection = isStartOfSection(position);
+		if (!isStartOfSection) {
+			View header = mAdapter.getHeaderView(position, null, mList);
+			if (header == null) {
+				throw new NullPointerException("header may not be null");
+			}
+			ensureHeaderHasCorrectLayoutParams(header);
+			measureHeader(header);
+			return header.getMeasuredHeight();
+		}
+		return 0;
+	}
+
+	/* ---------- StickyListHeaders specific API ---------- */
+
+	public void setAreHeadersSticky(boolean areHeadersSticky) {
+		mAreHeadersSticky = areHeadersSticky;
+		if (!areHeadersSticky) {
+			clearHeader();
+		} else {
+			updateOrClearHeader(mList.getFixedFirstVisibleItem());
+		}
+		// invalidating the list will trigger dispatchDraw()
+		mList.invalidate();
+	}
+
+	public boolean areHeadersSticky() {
+		return mAreHeadersSticky;
+	}
+
+	/**
+	 * Use areHeadersSticky() method instead
+	 */
+	@Deprecated
+	public boolean getAreHeadersSticky() {
+		return areHeadersSticky();
+	}
+
+	public void setDrawingListUnderStickyHeader(
+			boolean drawingListUnderStickyHeader) {
+		mIsDrawingListUnderStickyHeader = drawingListUnderStickyHeader;
+		// reset the top clipping length
+		mList.setTopClippingLength(0);
+	}
+
+	public boolean isDrawingListUnderStickyHeader() {
+		return mIsDrawingListUnderStickyHeader;
+	}
+
+	public void setOnHeaderClickListener(
+			OnHeaderClickListener onHeaderClickListener) {
+		mOnHeaderClickListener = onHeaderClickListener;
+		if (mAdapter != null) {
+			if (mOnHeaderClickListener != null) {
+				mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
+			} else {
+				mAdapter.setOnHeaderClickListener(null);
+			}
+		}
+	}
+
+	public View getListChildAt(int index) {
+		return mList.getChildAt(index);
+	}
+
+	public int getListChildCount() {
+		return mList.getChildCount();
+	}
+
+	/**
+	 * Use the method with extreme caution!! Changing any values on the
+	 * underlying ListView might break everything.
+	 * 
+	 * @return the ListView backing this view.
+	 */
+	public ListView getWrappedList() {
+		return mList;
+	}
+
+	/* ---------- ListView delegate methods ---------- */
+
+	public void setAdapter(StickyListHeadersAdapter adapter) {
+		if (adapter == null) {
+			mList.setAdapter(null);
+			clearHeader();
+			return;
+		}
+		if (mAdapter != null) {
+			mAdapter.unregisterDataSetObserver(mDataSetObserver);
+		}
+
+		if (adapter instanceof SectionIndexer) {
+			mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter);
+		} else {
+			mAdapter = new AdapterWrapper(getContext(), adapter);
+		}
+		mDataSetObserver = new AdapterWrapperDataSetObserver();
+		mAdapter.registerDataSetObserver(mDataSetObserver);
+
+		if (mOnHeaderClickListener != null) {
+			mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
+		} else {
+			mAdapter.setOnHeaderClickListener(null);
+		}
+
+		mAdapter.setDivider(mDivider, mDividerHeight);
+
+		mList.setAdapter(mAdapter);
+		clearHeader();
+	}
+
+	public StickyListHeadersAdapter getAdapter() {
+		return mAdapter == null ? null : mAdapter.mDelegate;
+	}
+
+	public void setDivider(Drawable divider) {
+		mDivider = divider;
+		if (mAdapter != null) {
+			mAdapter.setDivider(mDivider, mDividerHeight);
+		}
+	}
+
+	public void setDividerHeight(int dividerHeight) {
+		mDividerHeight = dividerHeight;
+		if (mAdapter != null) {
+			mAdapter.setDivider(mDivider, mDividerHeight);
+		}
+	}
+
+	public Drawable getDivider() {
+		return mDivider;
+	}
+
+	public int getDividerHeight() {
+		return mDividerHeight;
+	}
+
+	public void setOnScrollListener(OnScrollListener onScrollListener) {
+		mOnScrollListenerDelegate = onScrollListener;
+	}
+
+	public void setOnItemClickListener(OnItemClickListener listener) {
+		mList.setOnItemClickListener(listener);
+	}
+
+	public void setOnItemLongClickListener(OnItemLongClickListener listener) {
+		mList.setOnItemLongClickListener(listener);
+	}
+
+    public void addHeaderView(View v, Object data, boolean isSelectable) {
+        mList.addHeaderView(v, data, isSelectable);
+    }
+
+	public void addHeaderView(View v) {
+		mList.addHeaderView(v);
+	}
+
+	public void removeHeaderView(View v) {
+		mList.removeHeaderView(v);
+	}
+
+	public int getHeaderViewsCount() {
+		return mList.getHeaderViewsCount();
+	}
+
+	public void addFooterView(View v) {
+		mList.addFooterView(v);
+	}
+
+	public void removeFooterView(View v) {
+		mList.removeFooterView(v);
+	}
+
+	public int getFooterViewsCount() {
+		return mList.getFooterViewsCount();
+	}
+
+	public void setEmptyView(View v) {
+		mList.setEmptyView(v);
+	}
+
+	public View getEmptyView() {
+		return mList.getEmptyView();
+	}
+
+    @Override
+    public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
+        mList.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
+    }
+
+    @Override
+    public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
+        mList.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled);
+    }
+
+	@TargetApi(Build.VERSION_CODES.FROYO)
+	public void smoothScrollBy(int distance, int duration) {
+		requireSdkVersion(Build.VERSION_CODES.FROYO);
+		mList.smoothScrollBy(distance, duration);
+	}
+
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public void smoothScrollByOffset(int offset) {
+		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
+		mList.smoothScrollByOffset(offset);
+	}
+
+	@SuppressLint("NewApi")
+	@TargetApi(Build.VERSION_CODES.FROYO)
+	public void smoothScrollToPosition(int position) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+			mList.smoothScrollToPosition(position);
+		} else {
+			int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
+			offset -= mClippingToPadding ? 0 : mPaddingTop;
+			mList.smoothScrollToPositionFromTop(position, offset);
+		}
+	}
+
+	@TargetApi(Build.VERSION_CODES.FROYO)
+	public void smoothScrollToPosition(int position, int boundPosition) {
+		requireSdkVersion(Build.VERSION_CODES.FROYO);
+		mList.smoothScrollToPosition(position, boundPosition);
+	}
+
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public void smoothScrollToPositionFromTop(int position, int offset) {
+		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
+		offset += mAdapter == null ? 0 : getHeaderOverlap(position);
+		offset -= mClippingToPadding ? 0 : mPaddingTop;
+		mList.smoothScrollToPositionFromTop(position, offset);
+	}
+
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public void smoothScrollToPositionFromTop(int position, int offset,
+			int duration) {
+		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
+		offset += mAdapter == null ? 0 : getHeaderOverlap(position);
+		offset -= mClippingToPadding ? 0 : mPaddingTop;
+		mList.smoothScrollToPositionFromTop(position, offset, duration);
+	}
+
+	public void setSelection(int position) {
+		setSelectionFromTop(position, 0);
+	}
+
+	public void setSelectionAfterHeaderView() {
+		mList.setSelectionAfterHeaderView();
+	}
+
+	public void setSelectionFromTop(int position, int y) {
+		y += mAdapter == null ? 0 : getHeaderOverlap(position);
+		y -= mClippingToPadding ? 0 : mPaddingTop;
+		mList.setSelectionFromTop(position, y);
+	}
+
+	public void setSelector(Drawable sel) {
+		mList.setSelector(sel);
+	}
+
+	public void setSelector(int resID) {
+		mList.setSelector(resID);
+	}
+
+	public int getFirstVisiblePosition() {
+		return mList.getFirstVisiblePosition();
+	}
+
+	public int getLastVisiblePosition() {
+		return mList.getLastVisiblePosition();
+	}
+
+	public void setChoiceMode(int choiceMode) {
+		mList.setChoiceMode(choiceMode);
+	}
+
+	public void setItemChecked(int position, boolean value) {
+		mList.setItemChecked(position, value);
+	}
+
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public int getCheckedItemCount() {
+		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
+		return mList.getCheckedItemCount();
+	}
+
+	@TargetApi(Build.VERSION_CODES.FROYO)
+	public long[] getCheckedItemIds() {
+		requireSdkVersion(Build.VERSION_CODES.FROYO);
+		return mList.getCheckedItemIds();
+	}
+
+	public int getCheckedItemPosition() {
+		return mList.getCheckedItemPosition();
+	}
+
+	public SparseBooleanArray getCheckedItemPositions() {
+		return mList.getCheckedItemPositions();
+	}
+
+	public int getCount() {
+		return mList.getCount();
+	}
+
+	public Object getItemAtPosition(int position) {
+		return mList.getItemAtPosition(position);
+	}
+
+	public long getItemIdAtPosition(int position) {
+		return mList.getItemIdAtPosition(position);
+	}
+
+	public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
+		mList.setOnCreateContextMenuListener(l);
+	}
+
+	public boolean showContextMenu() {
+		return mList.showContextMenu();
+	}
+
+	public void invalidateViews() {
+		mList.invalidateViews();
+	}
+
+	@Override
+	public void setClipToPadding(boolean clipToPadding) {
+		if (mList != null) {
+			mList.setClipToPadding(clipToPadding);
+		}
+		mClippingToPadding = clipToPadding;
+	}
+
+	@Override
+	public void setPadding(int left, int top, int right, int bottom) {
+		mPaddingLeft = left;
+		mPaddingTop = top;
+		mPaddingRight = right;
+		mPaddingBottom = bottom;
+
+		if (mList != null) {
+			mList.setPadding(left, top, right, bottom);
+		}
+		super.setPadding(0, 0, 0, 0);
+		requestLayout();
+	}
+
+	@Override
+	public int getPaddingLeft() {
+		return mPaddingLeft;
+	}
+
+	@Override
+	public int getPaddingTop() {
+		return mPaddingTop;
+	}
+
+	@Override
+	public int getPaddingRight() {
+		return mPaddingRight;
+	}
+
+	@Override
+	public int getPaddingBottom() {
+		return mPaddingBottom;
+	}
+
+	public void setFastScrollEnabled(boolean fastScrollEnabled) {
+		mList.setFastScrollEnabled(fastScrollEnabled);
+	}
+
+	/**
+	 * @see android.widget.AbsListView#setFastScrollAlwaysVisible(boolean)
+	 * @throws ApiLevelTooLowException on pre-Honeycomb device.
+	 */
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
+		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+			mList.setFastScrollAlwaysVisible(alwaysVisible);
+		}
+	}
+
+	/**
+	 * @see android.widget.AbsListView#isFastScrollAlwaysVisible()
+	 * @return true if the fast scroller will always show. False on pre-Honeycomb devices.
+	 */
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public boolean isFastScrollAlwaysVisible(){
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+			return false;
+		}
+		return mList.isFastScrollAlwaysVisible();
+	}
+
+	private void requireSdkVersion(int versionCode) {
+		if (Build.VERSION.SDK_INT < versionCode) {
+			throw new ApiLevelTooLowException(versionCode);
+		}
+	}
+
+	public int getPositionForView(View view) {
+		return mList.getPositionForView(view);
+	}
+	
+
+	
+	
+	
+    private int mTotalCount;
+    private int mItemOffsetY[];
+    private boolean scrollIsComputed = false;
+    private int mHeight;
+    
+    
+	public int getListHeight() {
+        return mHeight;
+    }
+
+    public void computeScrollY() {
+        mHeight = 0;
+        mTotalCount = getAdapter().getCount();
+
+        int sectionHeight = 0;
+        int itemHeight = 0;
+        int desiredWidth = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
+
+        if (mItemOffsetY == null) {
+            mItemOffsetY = new int[mTotalCount];
+        }
+        for (int i = 0; i < mTotalCount; ++i) {
+
+            if (i == 0) {
+                View view = getAdapter().getView(i, null, this);
+                view.measure(desiredWidth, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+                sectionHeight = view.getMeasuredHeight();
+                mItemOffsetY[i] = mHeight;
+                mHeight += sectionHeight;
+            } else if (i == 1) {
+                View view = getAdapter().getView(i, null, this);
+                view.measure(desiredWidth, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+                itemHeight = view.getMeasuredHeight();
+                mItemOffsetY[i] = mHeight;
+                mHeight += itemHeight;
+            } else {
+                int type = getAdapter().getItemViewType(i);
+                switch (type) {
+                case ContactsAdapter.TYPE_CONTACT:
+                    mHeight += itemHeight;
+                case ContactsAdapter.TYPE_HEADER:
+                    mHeight += sectionHeight;
+                }
+                mItemOffsetY[i] = mHeight;
+                mHeight += sectionHeight;
+            }
+
+            System.out.println(mHeight);
+        }
+        scrollIsComputed = true;
+    }
+
+    public boolean scrollYIsComputed() {
+        return scrollIsComputed;
+    }
+
+    public int getComputedScrollY() {
+        int pos, nScrollY, nItemY;
+        View view = null;
+        pos = getFirstVisiblePosition();
+        view = getChildAt(0);
+        nItemY = view.getTop();
+        nScrollY = mItemOffsetY[pos] - nItemY;
+        return nScrollY;
+    }
+
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/WrapperView.java b/ring-android/src/cx/ring/views/stickylistheaders/WrapperView.java
new file mode 100644
index 0000000..9ee36ad
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/WrapperView.java
@@ -0,0 +1,150 @@
+package cx.ring.views.stickylistheaders;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * 
+ * the view that wrapps a divider header and a normal list item. The listview sees this as 1 item
+ * 
+ * @author Emil Sjölander
+ */
+public class WrapperView extends ViewGroup {
+
+	View mItem;
+	Drawable mDivider;
+	int mDividerHeight;
+	View mHeader;
+	int mItemTop;
+
+	WrapperView(Context c) {
+		super(c);
+	}
+
+	public boolean hasHeader() {
+		return mHeader != null;
+	}
+	
+	public View getItem() {
+		return mItem;
+	}
+	
+	public View getHeader() {
+		return mHeader;
+	}
+
+	void update(View item, View header, Drawable divider, int dividerHeight) {
+		
+		//every wrapperview must have a list item
+		if (item == null) {
+			throw new NullPointerException("List view item must not be null.");
+		}
+
+		//only remove the current item if it is not the same as the new item. this can happen if wrapping a recycled view
+		if (this.mItem != item) {
+			removeView(this.mItem);
+			this.mItem = item;
+			final ViewParent parent = item.getParent();
+			if(parent != null && parent != this) {
+				if(parent instanceof ViewGroup) {
+					((ViewGroup) parent).removeView(item);
+				}
+			}
+			addView(item);
+		}
+
+		//same logik as above but for the header
+		if (this.mHeader != header) {
+			if (this.mHeader != null) {
+				removeView(this.mHeader);
+			}
+			this.mHeader = header;
+			if (header != null) {
+				addView(header);
+			}
+		}
+
+		if (this.mDivider != divider) {
+			this.mDivider = divider;
+			this.mDividerHeight = dividerHeight;
+			invalidate();
+		}
+	}
+
+	@Override
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+		int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
+				MeasureSpec.EXACTLY);
+		int measuredHeight = 0;
+		
+		//measure header or divider. when there is a header visible it acts as the divider
+		if (mHeader != null) {
+			ViewGroup.LayoutParams params = mHeader.getLayoutParams();
+			if (params != null && params.height > 0) {
+				mHeader.measure(childWidthMeasureSpec,
+						MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY));
+			} else {
+				mHeader.measure(childWidthMeasureSpec,
+						MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+			}
+			measuredHeight += mHeader.getMeasuredHeight();
+		} else if (mDivider != null) {
+			measuredHeight += mDividerHeight;
+		}
+		
+		//measure item
+		ViewGroup.LayoutParams params = mItem.getLayoutParams();
+		if (params != null && params.height > 0) {
+			mItem.measure(childWidthMeasureSpec,
+					MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY));
+		} else {
+			mItem.measure(childWidthMeasureSpec,
+					MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+		}
+		measuredHeight += mItem.getMeasuredHeight();
+
+		setMeasuredDimension(measuredWidth, measuredHeight);
+	}
+
+	@Override
+	protected void onLayout(boolean changed, int l, int t, int r, int b) {
+
+		l = 0;
+		t = 0;
+		r = getWidth();
+		b = getHeight();
+
+		if (mHeader != null) {
+			int headerHeight = mHeader.getMeasuredHeight();
+			mHeader.layout(l, t, r, headerHeight);
+			mItemTop = headerHeight;
+			mItem.layout(l, headerHeight, r, b);
+		} else if (mDivider != null) {
+			mDivider.setBounds(l, t, r, mDividerHeight);
+			mItemTop = mDividerHeight;
+			mItem.layout(l, mDividerHeight, r, b);
+		} else {
+			mItemTop = t;
+			mItem.layout(l, t, r, b);
+		}
+	}
+
+	@Override
+	protected void dispatchDraw(Canvas canvas) {
+		super.dispatchDraw(canvas);
+		if (mHeader == null && mDivider != null) {
+			// Drawable.setBounds() does not seem to work pre-honeycomb. So have
+			// to do this instead
+			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+				canvas.clipRect(0, 0, getWidth(), mDividerHeight);
+			}
+			mDivider.draw(canvas);
+		}
+	}
+}
diff --git a/ring-android/src/cx/ring/views/stickylistheaders/WrapperViewList.java b/ring-android/src/cx/ring/views/stickylistheaders/WrapperViewList.java
new file mode 100644
index 0000000..c0c95d7
--- /dev/null
+++ b/ring-android/src/cx/ring/views/stickylistheaders/WrapperViewList.java
@@ -0,0 +1,176 @@
+package cx.ring.views.stickylistheaders;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+class WrapperViewList extends ListView {
+
+	interface LifeCycleListener {
+		void onDispatchDrawOccurred(Canvas canvas);
+	}
+
+	private LifeCycleListener mLifeCycleListener;
+	private List<View> mFooterViews;
+	private int mTopClippingLength;
+	private Rect mSelectorRect = new Rect();// for if reflection fails
+	private Field mSelectorPositionField;
+	private boolean mClippingToPadding = true;
+
+	public WrapperViewList(Context context, AttributeSet attrs) {
+		super(context, attrs);
+
+		// Use reflection to be able to change the size/position of the list
+		// selector so it does not come under/over the header
+		try {
+			Field selectorRectField = AbsListView.class.getDeclaredField("mSelectorRect");
+			selectorRectField.setAccessible(true);
+			mSelectorRect = (Rect) selectorRectField.get(this);
+
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+				mSelectorPositionField = AbsListView.class.getDeclaredField("mSelectorPosition");
+				mSelectorPositionField.setAccessible(true);
+			}
+		} catch (NoSuchFieldException e) {
+			e.printStackTrace();
+		} catch (IllegalArgumentException e) {
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public boolean performItemClick(View view, int position, long id) {
+		if (view instanceof WrapperView) {
+			view = ((WrapperView) view).mItem;
+		}
+		return super.performItemClick(view, position, id);
+	}
+
+	private void positionSelectorRect() {
+		if (!mSelectorRect.isEmpty()) {
+			int selectorPosition = getSelectorPosition();
+			if (selectorPosition >= 0) {
+				int firstVisibleItem = getFixedFirstVisibleItem();
+				View v = getChildAt(selectorPosition - firstVisibleItem);
+				if (v instanceof WrapperView) {
+					WrapperView wrapper = ((WrapperView) v);
+					mSelectorRect.top = wrapper.getTop() + wrapper.mItemTop;
+				}
+			}
+		}
+	}
+
+	private int getSelectorPosition() {
+		if (mSelectorPositionField == null) { // not all supported andorid
+												// version have this variable
+			for (int i = 0; i < getChildCount(); i++) {
+				if (getChildAt(i).getBottom() == mSelectorRect.bottom) {
+					return i + getFixedFirstVisibleItem();
+				}
+			}
+		} else {
+			try {
+				return mSelectorPositionField.getInt(this);
+			} catch (IllegalArgumentException e) {
+				e.printStackTrace();
+			} catch (IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+		return -1;
+	}
+
+	@Override
+	protected void dispatchDraw(Canvas canvas) {
+		positionSelectorRect();
+		if (mTopClippingLength != 0) {
+			canvas.save();
+			Rect clipping = canvas.getClipBounds();
+			clipping.top = mTopClippingLength;
+			canvas.clipRect(clipping);
+			super.dispatchDraw(canvas);
+			canvas.restore();
+		} else {
+			super.dispatchDraw(canvas);
+		}
+		mLifeCycleListener.onDispatchDrawOccurred(canvas);
+	}
+
+	void setLifeCycleListener(LifeCycleListener lifeCycleListener) {
+		mLifeCycleListener = lifeCycleListener;
+	}
+
+	@Override
+	public void addFooterView(View v) {
+		super.addFooterView(v);
+		if (mFooterViews == null) {
+			mFooterViews = new ArrayList<View>();
+		}
+		mFooterViews.add(v);
+	}
+
+	@Override
+	public boolean removeFooterView(View v) {
+		if (super.removeFooterView(v)) {
+			mFooterViews.remove(v);
+			return true;
+		}
+		return false;
+	}
+
+	boolean containsFooterView(View v) {
+		if (mFooterViews == null) {
+			return false;
+		}
+		return mFooterViews.contains(v);
+	}
+
+	void setTopClippingLength(int topClipping) {
+		mTopClippingLength = topClipping;
+	}
+
+	int getFixedFirstVisibleItem() {
+		int firstVisibleItem = getFirstVisiblePosition();
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+			return firstVisibleItem;
+		}
+
+		// first getFirstVisiblePosition() reports items
+		// outside the view sometimes on old versions of android
+		for (int i = 0; i < getChildCount(); i++) {
+			if (getChildAt(i).getBottom() >= 0) {
+				firstVisibleItem += i;
+				break;
+			}
+		}
+
+		// work around to fix bug with firstVisibleItem being to high
+		// because list view does not take clipToPadding=false into account
+		// on old versions of android
+		if (!mClippingToPadding && getPaddingTop() > 0 && firstVisibleItem > 0) {
+			if (getChildAt(0).getTop() > 0) {
+				firstVisibleItem -= 1;
+			}
+		}
+
+		return firstVisibleItem;
+	}
+
+	@Override
+	public void setClipToPadding(boolean clipToPadding) {
+		mClippingToPadding = clipToPadding;
+		super.setClipToPadding(clipToPadding);
+	}
+
+}