multi-device: add account creation/login

Tuleap: #953
Change-Id: I2599f5b8505609a556556b9fa6ca8ced3783cd3c
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java
index 12f6667..0c6d476 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java
@@ -89,7 +89,7 @@
 /*
         entryView.alias.setText(accounts.get(pos).getAlias());
 
-        entryView.host.setText(accounts.get(pos).getHost() + " - " + accounts.get(pos).getRegistered_state());
+        entryView.host.setText(accounts.get(pos).getHost() + " - " + accounts.get(pos).getRegistrationState());
         // accManager.displayAccountDetails(accounts.get(pos), entryView);
         entryView.error.setVisibility(View.GONE);
 */
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
index 38f63ca..b7d3e23 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
@@ -39,6 +39,7 @@
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
+import android.provider.OpenableColumns;
 import android.support.annotation.NonNull;
 import android.support.v4.app.Fragment;
 import android.support.v4.content.ContextCompat;
@@ -49,6 +50,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
@@ -56,12 +60,19 @@
 import android.widget.TextView.OnEditorActionListener;
 import android.widget.Toast;
 
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
 import java.util.HashMap;
 
 import cx.ring.R;
+import cx.ring.model.account.Account;
 import cx.ring.model.account.AccountDetail;
 import cx.ring.model.account.AccountDetailAdvanced;
 import cx.ring.model.account.AccountDetailBasic;
+import cx.ring.model.account.AccountDetailVolatile;
 import cx.ring.service.LocalService;
 
 public class AccountCreationFragment extends Fragment {
@@ -79,10 +90,18 @@
 
     // UI references.
     private LinearLayout mSipFormLinearLayout;
+    private LinearLayout mAddAccountLayout;
+    private LinearLayout mNewAccountLayout;
+
     private EditText mAliasView;
     private EditText mHostnameView;
     private EditText mUsernameView;
     private EditText mPasswordView;
+    //private EditText mRingUsername;
+    private EditText mRingPassword;
+    private EditText mRingPasswordRepeat;
+    private EditText mRingPin;
+    private EditText mRingAddPassword;
 
     private String mDataPath;
 
@@ -94,6 +113,22 @@
         super.onCreate(savedInstanceState);
     }
 
+    private void flipForm(boolean addacc, boolean newacc) {
+        mAddAccountLayout.setVisibility(addacc ? View.VISIBLE : View.GONE);
+        mNewAccountLayout.setVisibility(newacc ? View.VISIBLE : View.GONE);
+        InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (newacc) {
+            mRingPassword.requestFocus();
+            imm.showSoftInput(mRingPassword, InputMethodManager.SHOW_IMPLICIT);
+        } else if (addacc) {
+            mRingPin.requestFocus();
+            imm.showSoftInput(mRingPin, InputMethodManager.SHOW_IMPLICIT);
+        }
+        if (addacc || newacc) {
+            mSipFormLinearLayout.setVisibility(View.GONE);
+        }
+    }
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
         final View inflatedView = inflater.inflate(R.layout.frag_account_creation, parent, false);
@@ -102,7 +137,111 @@
         mHostnameView = (EditText) inflatedView.findViewById(R.id.hostname);
         mUsernameView = (EditText) inflatedView.findViewById(R.id.username);
         mPasswordView = (EditText) inflatedView.findViewById(R.id.password);
+        //mRingUsername = (EditText) inflatedView.findViewById(R.id.ring_alias);
+        mRingPassword = (EditText) inflatedView.findViewById(R.id.ring_password);
+        mRingPasswordRepeat = (EditText) inflatedView.findViewById(R.id.ring_password_repeat);
+        mRingPin = (EditText) inflatedView.findViewById(R.id.ring_add_pin);
+        mRingAddPassword = (EditText) inflatedView.findViewById(R.id.ring_add_password);
 
+        final Button ring_create_btn = (Button) inflatedView.findViewById(R.id.ring_create_btn);
+        final Button ring_add_btn = (Button) inflatedView.findViewById(R.id.ring_add_account);
+
+        /*mRingUsername.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence name, int i, int i1, int i2) {
+                LocalService service = mCallbacks.getService();
+                if (service == null)
+                    return;
+                service.getNameDirectory().findAddr(name.toString(), new LocalService.NameRequest() {
+                    @Override
+                    public void onResult(String res, Object err) {
+                        Log.w(TAG, "mRingUsername onResult " + res + " " + err);
+                        if (err == null && res != null && !res.isEmpty()) {
+                            mRingUsername.setError("Username already taken");
+                        } else {
+                            mRingUsername.setError(null);
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {}
+        });*/
+        mRingPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+                if (actionId == EditorInfo.IME_ACTION_NEXT)
+                    return checkPassword(v, null);
+                return false;
+            }
+        });
+        mRingPassword.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (!hasFocus) {
+                    checkPassword((TextView) v, null);
+                } else {
+                    //alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                }
+            }
+        });
+        mRingPasswordRepeat.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+                if (actionId == EditorInfo.IME_ACTION_DONE) {
+                    if (mRingPassword.getText().length() != 0 && !checkPassword(mRingPassword, v)) {
+                        ring_create_btn.callOnClick();
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+        mRingAddPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+                if (actionId == EditorInfo.IME_ACTION_DONE) {
+                    ring_add_btn.callOnClick();
+                    return true;
+                }
+                return false;
+            }
+        });
+
+        ring_create_btn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mNewAccountLayout.getVisibility() == View.GONE) {
+                    flipForm(false, true);
+                } else {
+                    if (!checkPassword(mRingPassword, mRingPasswordRepeat)) {
+                        mAccountType = AccountDetailBasic.ACCOUNT_TYPE_RING;
+                        //mAlias = mRingUsername.getText().toString();
+                        mUsername = mAlias;
+                        initAccountCreation(/*mRingUsername.getText().toString()*/null, null, mRingPassword.getText().toString());
+                    }
+                }
+            }
+        });
+        ring_add_btn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mAddAccountLayout.getVisibility() == View.GONE) {
+                    flipForm(true, false);
+                } else if (mRingPin.getText().length() != 0 && mRingAddPassword.getText().length() != 0) {
+                    mAccountType = AccountDetailBasic.ACCOUNT_TYPE_RING;
+                    mUsername = mAlias;
+                    initAccountCreation(null, mRingPin.getText().toString(), mRingAddPassword.getText().toString());
+                }
+            }
+        });
         mPasswordView.setOnEditorActionListener(new OnEditorActionListener() {
             @Override
             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -114,13 +253,13 @@
                 return true;
             }
         });
-        inflatedView.findViewById(R.id.ring_card_view).setOnClickListener(new View.OnClickListener() {
+        /*inflatedView.findViewById(R.id.ring_card_view).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 mAccountType = AccountDetailBasic.ACCOUNT_TYPE_RING;
                 initAccountCreation();
             }
-        });
+        });*/
         inflatedView.findViewById(R.id.create_sip_button).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
@@ -132,13 +271,15 @@
                 attemptCreation();
             }
         });
-        inflatedView.findViewById(R.id.import_card_view).setOnClickListener(new View.OnClickListener() {
+        /*inflatedView.findViewById(R.id.import_card_view).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 startImport();
             }
-        });
+        });*/
 
+        mNewAccountLayout = (LinearLayout) inflatedView.findViewById(R.id.newAccountLayout);
+        mAddAccountLayout = (LinearLayout) inflatedView.findViewById(R.id.addAccountLayout);
         mSipFormLinearLayout = (LinearLayout) inflatedView.findViewById(R.id.sipFormLinearLayout);
         mSipFormLinearLayout.setVisibility(View.GONE);
         inflatedView.findViewById(R.id.sipHeaderLinearLayout).setOnClickListener(new View.OnClickListener() {
@@ -146,6 +287,7 @@
             public void onClick(View v) {
                 if (null != mSipFormLinearLayout) {
                     if (mSipFormLinearLayout.getVisibility() != View.VISIBLE) {
+                        flipForm(false, false);
                         mSipFormLinearLayout.setVisibility(View.VISIBLE);
                         //~ Let the time to perform setVisibility before scrolling.
                         final ScrollView loginForm = (ScrollView) inflatedView.findViewById(R.id.login_form);
@@ -175,7 +317,7 @@
      * @author paulburke
      */
     @SuppressLint("NewApi")
-    public static String getPath(final Context context, final Uri uri) {
+    private static String getPath(final Context context, final Uri uri) {
 
         final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
 
@@ -247,8 +389,8 @@
      * @param selectionArgs (Optional) Selection arguments used in the query.
      * @return The value of the _data column, which is typically a file path.
      */
-    public static String getDataColumn(Context context, Uri uri, String selection,
-                                       String[] selectionArgs) {
+    private static String getDataColumn(Context context, Uri uri, String selection,
+                                        String[] selectionArgs) {
 
         Cursor cursor = null;
         final String column = "_data";
@@ -277,7 +419,7 @@
      * @param uri The Uri to check.
      * @return Whether the Uri authority is ExternalStorageProvider.
      */
-    public static boolean isExternalStorageDocument(Uri uri) {
+    private static boolean isExternalStorageDocument(Uri uri) {
         return "com.android.externalstorage.documents".equals(uri.getAuthority());
     }
 
@@ -285,7 +427,7 @@
      * @param uri The Uri to check.
      * @return Whether the Uri authority is DownloadsProvider.
      */
-    public static boolean isDownloadsDocument(Uri uri) {
+    private static boolean isDownloadsDocument(Uri uri) {
         return "com.android.providers.downloads.documents".equals(uri.getAuthority());
     }
 
@@ -293,7 +435,7 @@
      * @param uri The Uri to check.
      * @return Whether the Uri authority is MediaProvider.
      */
-    public static boolean isMediaDocument(Uri uri) {
+    private static boolean isMediaDocument(Uri uri) {
         return "com.android.providers.media.documents".equals(uri.getAuthority());
     }
 
@@ -303,6 +445,67 @@
         startActivityForResult(intent, FILE_SELECT_CODE);
     }
 
+    public static long copy(Reader input, Writer output) throws IOException {
+        char[] buffer = new char[8192];
+        long count = 0;
+        int n;
+        while ((n = input.read(buffer)) != -1) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    private int getDocumentSize(Uri uri) {
+        // The query, since it only applies to a single document, will only return
+        // one row. There's no need to filter, sort, or select fields, since we want
+        // all fields for one document.
+        Cursor cursor = getActivity().getContentResolver().query(uri, null, null, null, null, null);
+
+        try {
+            // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+            // "if there's anything to look at, look at it" conditionals.
+            if (cursor != null && cursor.moveToFirst()) {
+
+                // Note it's called "Display Name".  This is
+                // provider-specific, and might not necessarily be the file name.
+                //String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+                //Log.i(TAG, "Display Name: " + displayName);
+
+                int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+                // If the size is unknown, the value stored is null.  But since an
+                // int can't be null in Java, the behavior is implementation-specific,
+                // which is just a fancy term for "unpredictable".  So as
+                // a rule, check if it's null before assigning to an int.  This will
+                // happen often:  The storage API allows for remote files, whose
+                // size might not be locally known.
+                String size = null;
+                if (!cursor.isNull(sizeIndex)) {
+                    // Technically the column stores an int, but cursor.getString()
+                    // will do the conversion automatically.
+                    size = cursor.getString(sizeIndex);
+                    Log.i(TAG, "Size: " + size);
+                    return cursor.getInt(sizeIndex);
+                } else {
+                    size = "Unknown";
+                }
+                Log.i(TAG, "Size: " + size);
+
+            }
+        } finally {
+            cursor.close();
+        }
+        return 0;
+    }
+
+    private void readFromUri(Uri uri, String outPath) throws IOException {
+        if (getDocumentSize(uri) > 16 * 1024 * 1024) {
+            Toast.makeText(getActivity(), "File is too big", Toast.LENGTH_LONG).show();
+            throw new IOException("File is too big");
+        }
+        copy(new InputStreamReader(getActivity().getContentResolver().openInputStream(uri)), new FileWriter(outPath));
+    }
+
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
@@ -310,8 +513,16 @@
                 if (resultCode == Activity.RESULT_OK) {
                     Log.w(TAG, "onActivityResult " + data.getDataString());
                     this.mDataPath = getPath(getActivity(), data.getData());
-                    if (TextUtils.isEmpty(this.mDataPath))
-                        Toast.makeText(getActivity(), "Can't read " + data.getData(), Toast.LENGTH_LONG).show();
+                    if (TextUtils.isEmpty(this.mDataPath)) {
+                        try {
+                            this.mDataPath = getContext().getCacheDir().getPath() + "/temp.gz";
+                            readFromUri(data.getData(), this.mDataPath);
+                            showImportDialog();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                            Toast.makeText(getActivity(), "Can't read " + data.getData(), Toast.LENGTH_LONG).show();
+                        }
+                    }
                     else
                         showImportDialog();
                 }
@@ -434,7 +645,7 @@
      * 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() {
+    private void attemptCreation() {
 
         // Reset errors.
         mAliasView.setError(null);
@@ -474,7 +685,7 @@
         } else if (warningIPAccount) {
             showIP2IPDialog();
         } else {
-            initAccountCreation();
+            initAccountCreation(null, null, null);
         }
     }
 
@@ -485,7 +696,7 @@
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int whichButton) {
-                        initAccountCreation();
+                        initAccountCreation(null, null, null);
                     }
                 })
                 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -498,7 +709,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    private void initAccountCreation() {
+    private void initAccountCreation(String new_username, String pin, String password) {
         try {
             HashMap<String, String> accountDetails = (HashMap<String, String>) mCallbacks.getRemoteService().getAccountTemplate(mAccountType);
             accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_TYPE, mAccountType);
@@ -506,24 +717,30 @@
             boolean hasCameraPermission = ContextCompat.checkSelfPermission(getActivity(),
                     Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
             accountDetails.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, Boolean.toString(hasCameraPermission));
+
+            //~ Sipinfo is forced for any sipaccount since overrtp is not supported yet.
+            //~ This will have to be removed when it will be supported.
+            accountDetails.put(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE, getString(R.string.account_sip_dtmf_type_sipinfo));
+
             if (mAccountType.equals(AccountDetailBasic.ACCOUNT_TYPE_RING)) {
                 accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS, "Ring");
                 accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME, "bootstrap.ring.cx");
+                if (password != null && !password.isEmpty())
+                    accountDetails.put(AccountDetailBasic.CONFIG_ARCHIVE_PASSWORD, password);
+                if (pin != null && !pin.isEmpty()) {
+                    accountDetails.put(AccountDetailBasic.CONFIG_ARCHIVE_PIN, pin);
+                }
                 // Enable UPNP by default for Ring accounts
                 accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_UPNP_ENABLE, AccountDetail.TRUE_STR);
+                createNewAccount(accountDetails, mUsername);
             } else {
                 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);
+                createNewAccount(accountDetails, null);
             }
 
-            //~ Sipinfo is forced for any sipaccount since overrtp is not supported yet.
-            //~ This will have to be removed when it will be supported.
-            accountDetails.put(AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE,
-                    getString(R.string.account_sip_dtmf_type_sipinfo));
-
-            createNewAccount(accountDetails);
         } catch (RemoteException e) {
             Toast.makeText(getActivity(), "Error creating account", Toast.LENGTH_SHORT).show();
             e.printStackTrace();
@@ -531,8 +748,107 @@
 
     }
 
-    class CreateAccountTask extends AsyncTask<HashMap<String, String>, Void, String> {
+    private boolean checkPassword(@NonNull TextView pwd, TextView confirm) {
+        boolean error = false;
+        if (pwd.getText().length() == 0) {
+            error = true;
+        } else {
+            if (pwd.getText().length() < 6) {
+                pwd.setError(getString(R.string.error_password_char_count));
+                error = true;
+            } else {
+                pwd.setError(null);
+            }
+        }
+        if (confirm != null) {
+            if (!pwd.getText().toString().equals(confirm.getText().toString())) {
+                confirm.setError(getString(R.string.error_passwords_not_equals));
+                error = true;
+            } else {
+                confirm.setError(null);
+            }
+        }
+        return error;
+    }
+
+    /*private AlertDialog showPasswordDialog() {
+        Activity ownerActivity = getActivity();
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+        LayoutInflater inflater = ownerActivity.getLayoutInflater();
+        ViewGroup v = (ViewGroup) inflater.inflate(R.layout.dialog_account_export, null);
+        final TextView pwd = (TextView) v.findViewById(R.id.newpwd_txt);
+        final TextView pwd_confirm = (TextView) v.findViewById(R.id.newpwd_confirm_txt);
+        builder.setMessage(R.string.account_export_message)
+                .setTitle(R.string.account_export_title)
+                .setPositiveButton(R.string.account_export, null)
+                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                    }
+                }).setView(v);
+
+
+        final AlertDialog alertDialog = builder.create();
+        pwd.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+                if (actionId == EditorInfo.IME_ACTION_NEXT)
+                    return checkPassword(v, null);
+                return false;
+            }
+        });
+        pwd.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (!hasFocus) {
+                    checkPassword((TextView) v, null);
+                } else {
+                    alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                }
+            }
+        });
+        pwd_confirm.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+                if (actionId == EditorInfo.IME_ACTION_DONE) {
+                    if (!checkPassword(pwd, v)) {
+                        alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).callOnClick();
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+
+
+        alertDialog.setOwnerActivity(ownerActivity);
+        alertDialog.show();
+        alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (!checkPassword(pwd, pwd_confirm)) {
+                    final String pwd_txt = pwd.getText().toString();
+                    alertDialog.dismiss();
+                    initAccountCreation(pwd_txt);
+                }
+            }
+        });
+
+        return alertDialog;
+    }*/
+
+
+    private class CreateAccountTask extends AsyncTask<HashMap<String, String>, Void, String> {
         private ProgressDialog progress = null;
+        final private String username;
+
+        CreateAccountTask(String reg_username) {
+            Log.w(TAG, "CreateAccountTask ");
+            username = reg_username;
+        }
 
         @Override
         protected void onPreExecute() {
@@ -547,33 +863,66 @@
         @SafeVarargs
         @Override
         protected final String doInBackground(HashMap<String, String>... accs) {
-            try {
-                return mCallbacks.getRemoteService().addAccount(accs[0]);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-            return null;
-        }
-
-        @Override
-        protected void onPostExecute(String s) {
-            if (progress != null) {
-                progress.dismiss();
-                progress = null;
-            }
-            Intent resultIntent = new Intent();
-            getActivity().setResult(s.isEmpty() ? Activity.RESULT_CANCELED : Activity.RESULT_OK, resultIntent);
-            getActivity().finish();
+            final Account acc = mCallbacks.getService().createAccount(accs[0]);
+            acc.stateListener = new Account.OnStateChangedListener() {
+                @Override
+                public void stateChanged(String state, int code) {
+                    Log.w(TAG, "stateListener -> stateChanged " + state + " " + code);
+                    if (!AccountDetailVolatile.STATE_INITIALIZING.contentEquals(state)) {
+                        acc.stateListener = null;
+                        if (progress != null) {
+                            progress.dismiss();
+                            progress = null;
+                        }
+                        //Intent resultIntent = new Intent();
+                        AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity());
+                        dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int id) {
+                                //do things
+                            }
+                        });
+                        boolean success = false;
+                        switch (state) {
+                            case AccountDetailVolatile.STATE_ERROR_GENERIC:
+                                dialog.setTitle("Can't find account")
+                                        .setMessage("Account couldn't be found on the Ring network." +
+                                                "\nMake sure it was exported on Ring from an existing device, and that provided credentials are correct.");
+                                break;
+                            case AccountDetailVolatile.STATE_ERROR_NETWORK:
+                                dialog.setTitle("Can't connect to the network")
+                                        .setMessage("Could not add account because Ring coudn't connect to the distributed network. Check your device connectivity.");
+                                break;
+                            default:
+                                dialog.setTitle("Account device added")
+                                        .setMessage("You have successfully setup your Ring account on this device.");
+                                success = true;
+                                break;
+                        }
+                        AlertDialog d = dialog.show();
+                        if (success) {
+                            d.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                                @Override
+                                public void onDismiss(DialogInterface dialogInterface) {
+                                    getActivity().setResult(Activity.RESULT_OK, new Intent());
+                                    getActivity().finish();
+                                }
+                            });
+                        }
+                    }
+                }
+            };
+            Log.w(TAG, "Account created, registering " + username);
+            return acc.getAccountID();
         }
     }
 
-    private void createNewAccount(HashMap<String, String> accountDetails) {
+    private void createNewAccount(HashMap<String, String> accountDetails, String register_name) {
         if (creatingAccount)
             return;
         creatingAccount = true;
 
         //noinspection unchecked
-        new CreateAccountTask().execute(accountDetails);
+        new CreateAccountTask(register_name).execute(accountDetails);
     }
 
-}
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
index 37aae24..4fd1a0e 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
@@ -240,9 +240,9 @@
             entryView.alias.setText(item.getAlias());
 
             if (item.isIP2IP())
-                entryView.host.setText(item.getRegistered_state());
+                entryView.host.setText(item.getRegistrationState());
             else if (item.isSip())
-                entryView.host.setText(item.getHost() + " - " + item.getRegistered_state());
+                entryView.host.setText(item.getHost() + " - " + item.getRegistrationState());
             else
                 entryView.host.setText(item.getBasicDetails().getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME));
 
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/Account.java b/ring-android/app/src/main/java/cx/ring/model/account/Account.java
index a65282d..fba2ee5 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/Account.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/Account.java
@@ -32,13 +32,25 @@
 public class Account extends java.util.Observable {
     private static final String TAG = "Account";
 
-    final String accountID;
+    private final String accountID;
     private AccountDetailBasic basicDetails = null;
     private AccountDetailAdvanced advancedDetails = null;
     private AccountDetailSrtp srtpDetails = null;
     private AccountDetailTls tlsDetails = null;
     private AccountDetailVolatile volatileDetails = null;
-    private ArrayList<AccountCredentials> credentialsDetails;
+    private ArrayList<AccountCredentials> credentialsDetails = new ArrayList<>();
+
+    private Map<String, String> devices = new HashMap<>();
+
+    public OnDevicesChangedListener devicesListener = null;
+    public OnExportEndedListener exportListener = null;
+    public OnStateChangedListener stateListener = null;
+
+    public Account(String bAccountID) {
+        accountID = bAccountID;
+        volatileDetails = new AccountDetailVolatile();
+        basicDetails = new AccountDetailBasic();
+    }
 
     public Account(String bAccountID, final Map<String, String> details, final ArrayList<Map<String, String>> credentials, final Map<String, String> volatile_details) {
         accountID = bAccountID;
@@ -48,14 +60,56 @@
         tlsDetails = new AccountDetailTls(details);
         if (volatile_details != null)
             volatileDetails = new AccountDetailVolatile(volatile_details);
+        setCredentials(credentials);
+    }
+
+    public void update(Account acc) {
+        String old = getRegistrationState();
+        basicDetails = acc.basicDetails;
+        advancedDetails = acc.advancedDetails;
+        srtpDetails = acc.srtpDetails;
+        tlsDetails = acc.tlsDetails;
+        volatileDetails = acc.volatileDetails;
+        credentialsDetails = acc.credentialsDetails;
+        devices = acc.devices;
+        String new_reg_state = getRegistrationState();
+        if (!old.contentEquals(new_reg_state)) {
+            if (stateListener != null) {
+                stateListener.stateChanged(new_reg_state, getRegistrationStateCode());
+            }
+        }
+    }
+
+    public Map<String, String> getDevices() {
+        return devices;
+    }
+
+    public void setCredentials(ArrayList<Map<String, String>> credentials) {
+        credentialsDetails.clear();
         if (credentials != null) {
-            credentialsDetails = new ArrayList<>();
+            credentialsDetails.ensureCapacity(credentials.size());
             for (int i = 0; i < credentials.size(); ++i) {
                 credentialsDetails.add(new AccountCredentials(credentials.get(i)));
             }
         }
     }
 
+    public interface OnDevicesChangedListener {
+        void devicesChanged(Map<String, String> devices);
+    }
+    public interface OnExportEndedListener {
+        void exportEnded(int code, String pin);
+    }
+    public interface OnStateChangedListener {
+        void stateChanged(String state, int code);
+    }
+
+    public void setDevices(Map<String, String> devs) {
+        devices = devs;
+        if (devicesListener != null)
+            devicesListener.devicesChanged(devs);
+    }
+
     public String getAccountID() {
         return accountID;
     }
@@ -76,18 +130,37 @@
         basicDetails.setDetailString(AccountDetailBasic.CONFIG_ACCOUNT_ROUTESET, proxy);
     }
 
-    public String getRegistered_state() {
+    public String getRegistrationState() {
         return volatileDetails.getDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATUS);
     }
+    public int getRegistrationStateCode() {
+        String code_str = volatileDetails.getDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE);
+        if (code_str == null || code_str.isEmpty())
+            return 0;
+        return Integer.parseInt(code_str);
+    }
 
     public void setRegistrationState(String registered_state, int code) {
         Log.i(TAG, "setRegistrationState " + registered_state + " " + code);
+        String old = getRegistrationState();
         volatileDetails.setDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATUS, registered_state);
         volatileDetails.setDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE, Integer.toString(code));
+        if (!old.contentEquals(registered_state)) {
+            if (stateListener != null) {
+                stateListener.stateChanged(registered_state, code);
+            }
+        }
     }
 
     public void setVolatileDetails(Map<String, String> volatile_details) {
+        String state_old = getRegistrationState();
         volatileDetails = new AccountDetailVolatile(volatile_details);
+        String state_new = getRegistrationState();
+        if (!state_old.contentEquals(state_new)) {
+            if (stateListener != null) {
+                stateListener.stateChanged(state_new, getRegistrationStateCode());
+            }
+        }
     }
 
     public String getAlias() {
@@ -110,8 +183,8 @@
         return basicDetails;
     }
 
-    public void setBasicDetails(AccountDetailBasic basicDetails) {
-        this.basicDetails = basicDetails;
+    public void setBasicDetails(final Map<String, String> details) {
+        this.basicDetails = new AccountDetailBasic(details);
     }
 
     public AccountDetailAdvanced getAdvancedDetails() {
@@ -158,15 +231,15 @@
     }
 
     public boolean isTrying() {
-        return getRegistered_state().contentEquals(AccountDetailVolatile.STATE_TRYING);
+        return getRegistrationState().contentEquals(AccountDetailVolatile.STATE_TRYING);
     }
 
     public boolean isRegistered() {
-        return (getRegistered_state().contentEquals(AccountDetailVolatile.STATE_READY) || getRegistered_state().contentEquals(AccountDetailVolatile.STATE_REGISTERED));
+        return (getRegistrationState().contentEquals(AccountDetailVolatile.STATE_READY) || getRegistrationState().contentEquals(AccountDetailVolatile.STATE_REGISTERED));
     }
 
     public boolean isInError() {
-        String state = getRegistered_state();
+        String state = getRegistrationState();
         return (state.contentEquals(AccountDetailVolatile.STATE_ERROR)
                 || state.contentEquals(AccountDetailVolatile.STATE_ERROR_AUTH)
                 || state.contentEquals(AccountDetailVolatile.STATE_ERROR_CONF_STUN)
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java
index e92494d..4870920 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java
@@ -79,7 +79,7 @@
             CONFIG_PUBLISHED_SAMEAS_LOCAL,
             CONFIG_STUN_ENABLE, CONFIG_TURN_ENABLE));
 
-    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+    private final ArrayList<AccountDetail.PreferenceEntry> privateArray;
 
     public AccountDetailAdvanced(Map<String, String> pref) {
         privateArray = new ArrayList<>(pref.size());
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
index 1a78022..3047552 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
@@ -51,6 +51,10 @@
 
     public static final String CONFIG_PRESENCE_ENABLE = "Account.presenceEnabled";
 
+    public static final String CONFIG_ARCHIVE_PASSWORD = "Account.archivePassword";
+    public static final String CONFIG_ARCHIVE_PIN = "Account.archivePIN";
+    public static final String CONFIG_ETH_ACCOUNT = "ETH.account";
+
     public static final String ACCOUNT_TYPE_RING = "RING";
     public static final String ACCOUNT_TYPE_SIP = "SIP";
 
@@ -60,7 +64,7 @@
             CONFIG_ACCOUNT_UPNP_ENABLE,
             CONFIG_VIDEO_ENABLED));
 
-    private ArrayList<AccountDetail.PreferenceEntry> privateArray;
+    private final ArrayList<AccountDetail.PreferenceEntry> privateArray;
 
     public String getAlias() {
         return getDetailString(CONFIG_ACCOUNT_ALIAS);
@@ -74,6 +78,10 @@
         return getDetailString(CONFIG_ACCOUNT_HOSTNAME);
     }
 
+    public AccountDetailBasic() {
+        privateArray = new ArrayList<>();
+    }
+
     public AccountDetailBasic(Map<String, String> pref) {
         privateArray = new ArrayList<>(pref.size());
 
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java
index 4d4bf95..b5937b7 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java
@@ -43,11 +43,10 @@
     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;
+    private final ArrayList<AccountDetail.PreferenceEntry> privateArray = getPreferenceEntries();
 
     public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() {
         ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<>();
-
         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));
@@ -61,13 +60,10 @@
         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(Map<String, String> pref) {
-        privateArray = getPreferenceEntries();
-
         for (AccountDetail.PreferenceEntry p : privateArray) {
             p.mValue = pref.get(p.mKey);
         }
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailVolatile.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailVolatile.java
index 3f39d9e..26718e4 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailVolatile.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailVolatile.java
@@ -49,12 +49,13 @@
     public static final String STATE_ERROR_SERVICE_UNAVAILABLE  = "ERROR_SERVICE_UNAVAILABLE";
     public static final String STATE_ERROR_NOT_ACCEPTABLE       = "ERROR_NOT_ACCEPTABLE";
     public static final String STATE_REQUEST_TIMEOUT            = "Request Timeout";
+    public static final String STATE_INITIALIZING               = "INITIALIZING";
 
-    private ArrayList<PreferenceEntry> privateArray;
+    private final ArrayList<PreferenceEntry> privateArray = new ArrayList<>();
 
+    public AccountDetailVolatile() {
+    }
     public AccountDetailVolatile(Map<String, String> pref) {
-        privateArray = new ArrayList<PreferenceEntry>();
-
         for (String key : pref.keySet()) {
             PreferenceEntry p = new PreferenceEntry(key);
             p.mValue = pref.get(key);
diff --git a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
index 7562d04..39bac20 100644
--- a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
+++ b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
@@ -28,11 +28,13 @@
 
 import cx.ring.BuildConfig;
 
-public class ConfigurationManagerCallback extends ConfigurationCallback {
+class ConfigurationManagerCallback extends ConfigurationCallback {
 
     private static final String TAG = ConfigurationManagerCallback.class.getSimpleName();
 
     static public final String ACCOUNTS_CHANGED = BuildConfig.APPLICATION_ID + "accounts.changed";
+    static public final String ACCOUNTS_DEVICES_CHANGED = BuildConfig.APPLICATION_ID + "accounts.devicesChanged";
+    static public final String ACCOUNTS_EXPORT_ENDED = BuildConfig.APPLICATION_ID + "accounts.exportEnded";
     static public final String ACCOUNT_STATE_CHANGED = BuildConfig.APPLICATION_ID + "account.stateChanged";
     static public final String INCOMING_TEXT = BuildConfig.APPLICATION_ID + ".message.incomingTxt";
     static public final String MESSAGE_STATE_CHANGED = BuildConfig.APPLICATION_ID + ".message.stateChanged";
@@ -116,17 +118,39 @@
         OpenSlParams audioParams = OpenSlParams.createInstance(mService);
         ret.add(audioParams.getSampleRate());
         ret.add(audioParams.getBufferSize());
-        Log.d(getClass().getName(), "getHardwareAudioFormat: " + audioParams.getSampleRate() + " " + audioParams.getBufferSize());
+        Log.d(TAG, "getHardwareAudioFormat: " + audioParams.getSampleRate() + " " + audioParams.getBufferSize());
     }
 
     @Override
     public void getAppDataPath(String name, StringVect ret) {
-        if (name.equals("files"))
-            ret.add(mService.getFilesDir().getAbsolutePath());
-        else if (name.equals("cache"))
-            ret.add(mService.getCacheDir().getAbsolutePath());
-        else
-            ret.add(mService.getDir(name, Context.MODE_PRIVATE).getAbsolutePath());
+        switch (name) {
+            case "files":
+                ret.add(mService.getFilesDir().getAbsolutePath());
+                break;
+            case "cache":
+                ret.add(mService.getCacheDir().getAbsolutePath());
+                break;
+            default:
+                ret.add(mService.getDir(name, Context.MODE_PRIVATE).getAbsolutePath());
+                break;
+        }
     }
 
+    @Override
+    public void knownDevicesChanged(String account, StringMap devices) {
+        Intent intent = new Intent(ACCOUNTS_DEVICES_CHANGED);
+        intent.putExtra("account", account);
+        intent.putExtra("devices", devices.toNative());
+        mService.sendBroadcast(intent);
+    }
+
+    @Override
+    public void exportOnRingEnded(String account, int code, String pin) {
+        Log.w(TAG, "exportOnRingEnded: " + account + " " + code + " " + pin);
+        Intent intent = new Intent(ACCOUNTS_EXPORT_ENDED);
+        intent.putExtra("account", account);
+        intent.putExtra("code", code);
+        intent.putExtra("pin", pin);
+        mService.sendBroadcast(intent);
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
index 30b288d..b74fdd4 100644
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
@@ -852,6 +852,27 @@
             });
         }
 
+        @Override
+        public String exportOnRing(final String accountId, final String password) {
+            return getExecutor().executeAndReturn(new SipRunnableWithReturn<String>() {
+                @Override
+                protected String doRun() throws SameThreadException {
+                    Log.i(TAG, "DRingService.addRingDevice() thread running...");
+                    return Ringservice.exportOnRing(accountId, password);
+                }
+            });
+        }
+
+        public Map<String, String> getKnownRingDevices(final String accountId) {
+            return getExecutor().executeAndReturn(new SipRunnableWithReturn<Map<String, String>>() {
+                @Override
+                protected Map<String, String> doRun() throws SameThreadException {
+                    Log.i(TAG, "DRingService.getKnownRingDevices() thread running...");
+                    return Ringservice.getKnownRingDevices(accountId).toNative();
+                }
+            });
+        }
+
         /*************************
          * Transfer related API
          *************************/
diff --git a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
index a4ffc7e..020ea0b 100644
--- a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
+++ b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
@@ -49,6 +49,8 @@
     String getCurrentAudioOutputPlugin();
     List getCodecList(in String accountID);
     void setActiveCodecList(in List codecs, in String accountID);
+    String exportOnRing(in String accountID, in String password);
+    Map getKnownRingDevices(in String accountID);
 
     Map validateCertificatePath(in String accountID, in String certificatePath, in String privateKeyPath, in String privateKeyPass);
     Map validateCertificate(in String accountID, in String certificateId);
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
index 5b459cc..04990a2 100644
--- a/ring-android/app/src/main/java/cx/ring/service/LocalService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
@@ -91,9 +91,11 @@
 import cx.ring.model.SipUri;
 import cx.ring.model.TextMessage;
 import cx.ring.model.account.Account;
-import cx.ring.model.account.AccountDetailAdvanced;
+import cx.ring.model.account.AccountCredentials;
+import cx.ring.model.account.AccountDetailBasic;
 import cx.ring.model.account.AccountDetailSrtp;
 import cx.ring.model.account.AccountDetailTls;
+import cx.ring.model.account.AccountDetailVolatile;
 import cx.ring.utils.MediaManager;
 
 public class LocalService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -216,10 +218,26 @@
         return conf;
     }
 
+    public Account createAccount(HashMap<String, String> conf) {
+        Account acc = null;
+        try {
+            final String acc_id = mService.addAccount(conf);
+            acc = getAccount(acc_id);
+            if (acc == null) {
+                acc = new Account(acc_id);
+                accounts.add(acc);
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return acc;
+    }
+
     public void sendTextMessage(String account, SipUri to, String txt) {
         try {
             long id = mService.sendAccountTextMessage(account, to.getRawUriString(), txt);
             Log.w(TAG, "sendAccountTextMessage " + txt + " got id " + id);
+            Log.w(TAG, "sendAccountTextMessage " + txt + " got id " + id);
             TextMessage message = new TextMessage(false, txt, to, null, account);
             message.setID(id);
             message.read();
@@ -378,29 +396,22 @@
         @Override
         public void onLoadComplete(Loader<ArrayList<Account>> loader, ArrayList<Account> data) {
             Log.w(TAG, "AccountsLoader Loader.OnLoadCompleteListener " + data.size());
-            accounts = data;
+            ArrayList<Account> naccs = new ArrayList<>(data.size());
+            for (Account acc : data) {
+                Account f = getAccount(acc.getAccountID());
+                if (f != null)
+                    f.update(acc);
+                else
+                    f = acc;
+                naccs.add(f);
+            }
+            accounts = naccs;
             mAccountLoader.stopLoading();
             boolean haveSipAccount = false;
             boolean haveRingAccount = false;
             for (Account acc : accounts) {
-                //~ Sipinfo is forced for any sipaccount since overrtp is not supported yet.
-                //~ This will have to be removed when it will be supported.
-                Log.d(TAG, "Settings SIP DTMF type to sipinfo");
-                acc.getAdvancedDetails().setDetailString(
-                        AccountDetailAdvanced.CONFIG_ACCOUNT_DTMF_TYPE,
-                        getString(R.string.account_sip_dtmf_type_sipinfo)
-                );
-
-                try {
-                    final IDRingService remote = getRemoteService();
-                    remote.setAccountDetails(acc.getAccountID(), acc.getDetails());
-                } catch (android.os.RemoteException exception) {
-                    Log.e(TAG, "AccountsLoader", exception);
-                }
-
-                if (!acc.isEnabled()) {
+                if (!acc.isEnabled())
                     continue;
-                }
                 if (acc.isSip()) {
                     haveSipAccount = true;
                 } else if (acc.isRing()) {
@@ -1295,13 +1306,25 @@
                         mAccountLoader.startLoading();
                         mAccountLoader.onContentChanged();
                     } else {
-                        for (Account a : accounts) {
-                            if (a.getAccountID().contentEquals(intent.getStringExtra("account"))) {
+                        Account a = getAccount(intent.getStringExtra("account"));
+                        if (a != null) {
+                            String state_old = a.getRegistrationState();
+                            String state_new = intent.getStringExtra("state");
+                            if (state_old.contentEquals(AccountDetailVolatile.STATE_INITIALIZING) &&
+                                    !state_new.contentEquals(AccountDetailVolatile.STATE_INITIALIZING)) {
+                                try {
+                                    a.setBasicDetails((Map<String, String>) mService.getAccountDetails(a.getAccountID()));
+                                    a.setCredentials((ArrayList<Map<String, String>>) mService.getCredentials(a.getAccountID()));
+                                    a.setDevices((Map<String, String>) mService.getKnownRingDevices(a.getAccountID()));
+                                    a.setVolatileDetails((Map<String, String>) mService.getVolatileAccountDetails(a.getAccountID()));
+                                } catch (RemoteException e) {
+                                    e.printStackTrace();
+                                }
+                            } else {
                                 a.setRegistrationState(intent.getStringExtra("state"), intent.getIntExtra("code", 0));
-                                sendBroadcast(new Intent(ACTION_ACCOUNT_UPDATE));
-                                break;
                             }
                         }
+                        sendBroadcast(new Intent(ACTION_ACCOUNT_UPDATE));
                     }
                     break;
                 case ConfigurationManagerCallback.ACCOUNTS_CHANGED:
@@ -1310,6 +1333,18 @@
                         mAccountLoader.onContentChanged();
                     }
                     break;
+                case ConfigurationManagerCallback.ACCOUNTS_DEVICES_CHANGED: {
+                    Account acc = getAccount(intent.getStringExtra("account"));
+                    acc.setDevices((Map<String, String>) intent.getSerializableExtra("devices"));
+                    break;
+                }
+                case ConfigurationManagerCallback.ACCOUNTS_EXPORT_ENDED: {
+                    Account acc = getAccount(intent.getStringExtra("account"));
+                    if (acc != null && acc.exportListener != null) {
+                        acc.exportListener.exportEnded(intent.getIntExtra("code", -1), intent.getStringExtra("pin"));
+                    }
+                    break;
+                }
                 case CallManagerCallBack.INCOMING_TEXT:
                 case ConfigurationManagerCallback.INCOMING_TEXT: {
                     String message = intent.getStringExtra("txt");
@@ -1470,6 +1505,8 @@
 
         intentFilter.addAction(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED);
         intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_CHANGED);
+        intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_EXPORT_ENDED);
+        intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_DEVICES_CHANGED);
         intentFilter.addAction(ConfigurationManagerCallback.INCOMING_TEXT);
         intentFilter.addAction(ConfigurationManagerCallback.MESSAGE_STATE_CHANGED);
 
diff --git a/ring-android/app/src/main/java/cx/ring/views/BoundedLinearLayout.java b/ring-android/app/src/main/java/cx/ring/views/BoundedLinearLayout.java
new file mode 100644
index 0000000..f19dbee
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/BoundedLinearLayout.java
@@ -0,0 +1,46 @@
+package cx.ring.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import cx.ring.R;
+
+public class BoundedLinearLayout extends LinearLayout {
+
+    private final int mBoundedWidth;
+
+    private final int mBoundedHeight;
+
+    public BoundedLinearLayout(Context context) {
+        super(context);
+        mBoundedWidth = 0;
+        mBoundedHeight = 0;
+    }
+
+    public BoundedLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoundedView);
+        mBoundedWidth = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_width, 0);
+        mBoundedHeight = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_height, 0);
+        a.recycle();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Adjust width as necessary
+        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+        if(mBoundedWidth > 0 && mBoundedWidth < measuredWidth) {
+            int measureMode = MeasureSpec.getMode(widthMeasureSpec);
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedWidth, measureMode);
+        }
+        // Adjust height as necessary
+        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        if(mBoundedHeight > 0 && mBoundedHeight < measuredHeight) {
+            int measureMode = MeasureSpec.getMode(heightMeasureSpec);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedHeight, measureMode);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/jni/configurationmanager.i b/ring-android/app/src/main/jni/configurationmanager.i
index a8d92a8..384a039 100644
--- a/ring-android/app/src/main/jni/configurationmanager.i
+++ b/ring-android/app/src/main/jni/configurationmanager.i
@@ -34,6 +34,8 @@
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, uint64_t /*message_id*/, const std::string& /*to*/, int /*state*/){}
+    virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
+    virtual void exportOnRingEnded(const std::string& /*account_id*/, int /*state*/, const std::string& /*pin*/){}
 
     virtual void incomingTrustRequest(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& /*payload*/, time_t received){}
 
@@ -73,6 +75,8 @@
 std::map<std::string, std::string> getCodecDetails(const std::string& accountID, const unsigned& codecId);
 bool setCodecDetails(const std::string& accountID, const unsigned& codecId, const std::map<std::string, std::string>& details);
 std::vector<unsigned> getActiveCodecList(const std::string& accountID);
+std::string exportOnRing(const std::string& accountID, const std::string& password);
+std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID);
 
 void setActiveCodecList(const std::string& accountID, const std::vector<unsigned>& list);
 
@@ -183,6 +187,8 @@
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, uint64_t /*message_id*/, const std::string& /*to*/, int /*state*/){}
+    virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
+    virtual void exportOnRingEnded(const std::string& /*account_id*/, int /*state*/, const std::string& /*pin*/){}
 
     virtual void incomingTrustRequest(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& /*payload*/, time_t received){}
 
diff --git a/ring-android/app/src/main/jni/jni_interface.i b/ring-android/app/src/main/jni/jni_interface.i
index 4a5dab5..bdf7767 100644
--- a/ring-android/app/src/main/jni/jni_interface.i
+++ b/ring-android/app/src/main/jni/jni_interface.i
@@ -218,6 +218,8 @@
         exportable_callback<ConfigurationSignal::StunStatusFailed>(bind(&ConfigurationCallback::stunStatusFailure, confM, _1)),
         exportable_callback<ConfigurationSignal::RegistrationStateChanged>(bind(&ConfigurationCallback::registrationStateChanged, confM, _1, _2, _3, _4)),
         exportable_callback<ConfigurationSignal::VolatileDetailsChanged>(bind(&ConfigurationCallback::volatileAccountDetailsChanged, confM, _1, _2)),
+        exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&ConfigurationCallback::knownDevicesChanged, confM, _1, _2)),
+        exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&ConfigurationCallback::exportOnRingEnded, confM, _1, _2, _3)),
         exportable_callback<ConfigurationSignal::Error>(bind(&ConfigurationCallback::errorAlert, confM, _1)),
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&ConfigurationCallback::incomingAccountMessage, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&ConfigurationCallback::accountMessageStatusChanged, confM, _1, _2, _3, _4 )),
diff --git a/ring-android/app/src/main/res/layout/frag_account_creation.xml b/ring-android/app/src/main/res/layout/frag_account_creation.xml
index a6249f7..82ef5cd 100644
--- a/ring-android/app/src/main/res/layout/frag_account_creation.xml
+++ b/ring-android/app/src/main/res/layout/frag_account_creation.xml
@@ -19,33 +19,35 @@
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/login_form"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:background="#eeeeee"
     tools:context=".client.AccountWizard">
 
-    <LinearLayout
+    <cx.ring.views.BoundedLinearLayout
         style="@style/AccountFormContainer"
+        android:layout_gravity="center_horizontal"
+        android:animateLayoutChanges="false"
         android:descendantFocusability="beforeDescendants"
         android:focusableInTouchMode="true"
-        android:orientation="vertical">
+        android:orientation="vertical"
+        app:bounded_width="320dp">
 
-        <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
+        <android.support.v7.widget.CardView
             android:id="@+id/ring_card_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:layout_marginBottom="8dp"
-            android:clickable="true"
-            android:foreground="?android:attr/selectableItemBackground"
-            card_view:cardCornerRadius="2dp">
+            android:layout_marginBottom="16dp"
+            android:animateLayoutChanges="false">
 
             <RelativeLayout
                 android:id="@+id/ring_fields"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:animateLayoutChanges="true"
                 android:orientation="vertical">
 
                 <ImageView
@@ -71,8 +73,7 @@
                     android:paddingLeft="16dp"
                     android:paddingRight="16dp"
                     android:paddingTop="24dp"
-                    android:singleLine="false"
-                    android:text="@string/help_ring_title"
+                    android:text="Ring account"
                     android:textColor="@color/text_color_primary"
                     android:textSize="24sp" />
 
@@ -87,72 +88,103 @@
                     android:paddingLeft="16dp"
                     android:paddingRight="16dp"
                     android:paddingTop="16dp"
-                    android:singleLine="false"
                     android:text="@string/help_ring"
                     android:textColor="@color/text_color_primary"
                     android:textSize="14sp" />
 
-            </RelativeLayout>
+                <LinearLayout
+                    android:id="@+id/addAccountLayout"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_below="@id/textView"
+                    android:orientation="vertical"
+                    android:visibility="gone">
 
-        </android.support.v7.widget.CardView>
+                    <EditText
+                        android:id="@+id/ring_add_pin"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginBottom="8dp"
+                        android:layout_marginEnd="12dp"
+                        android:layout_marginLeft="12dp"
+                        android:layout_marginRight="12dp"
+                        android:layout_marginStart="12dp"
+                        android:hint="Enter PIN"
+                        android:maxLines="1"
+                        android:inputType="text"
+                        android:imeOptions="actionNext" />
 
-        <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
-            android:id="@+id/import_card_view"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:layout_marginBottom="8dp"
-            android:clickable="true"
-            android:foreground="?android:attr/selectableItemBackground"
-            card_view:cardCornerRadius="2dp">
+                    <EditText
+                        android:id="@+id/ring_add_password"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginBottom="8dp"
+                        android:layout_marginEnd="12dp"
+                        android:layout_marginLeft="12dp"
+                        android:layout_marginRight="12dp"
+                        android:layout_marginStart="12dp"
+                        android:hint="Enter your password"
+                        android:inputType="textPassword" />
+                </LinearLayout>
 
-            <RelativeLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:nestedScrollingEnabled="false"
-                android:orientation="vertical">
-
-                <TextView
-                    android:id="@+id/import_acc_title_txt"
+                <Button
+                    android:id="@+id/ring_add_account"
+                    style="@style/Widget.AppCompat.Button.Borderless.Colored"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_alignParentLeft="true"
-                    android:layout_alignParentStart="false"
-                    android:layout_alignParentTop="true"
-                    android:paddingLeft="16dp"
-                    android:paddingRight="16dp"
-                    android:paddingTop="24dp"
-                    android:singleLine="false"
-                    android:text="@string/account_import_title"
-                    android:textColor="@color/text_color_primary"
-                    android:textSize="24sp" />
+                    android:layout_below="@id/addAccountLayout"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:text="Add existing account" />
 
-                <ImageView
-                    android:id="@+id/imageView4"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentEnd="true"
-                    android:layout_alignParentRight="true"
-                    android:layout_alignParentTop="true"
-                    android:layout_margin="16dp"
-                    android:contentDescription="@string/account_import_title"
-                    android:src="@drawable/ic_archive_black_48dp" />
-
-                <TextView
-                    android:id="@+id/importAccountExplanation"
+                <LinearLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentLeft="true"
-                    android:layout_alignParentStart="true"
-                    android:layout_below="@+id/import_acc_title_txt"
-                    android:paddingBottom="24dp"
-                    android:paddingLeft="16dp"
-                    android:paddingRight="16dp"
-                    android:paddingTop="16dp"
-                    android:singleLine="false"
-                    android:text="@string/account_import_explanation"
-                    android:textColor="@color/text_color_primary"
-                    android:textSize="14sp" />
+                    android:layout_height="match_parent"
+                    android:layout_below="@id/ring_add_account"
+                    android:background="@color/color_primary_light"
+                    android:orientation="vertical">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:orientation="vertical"
+                        android:id="@+id/newAccountLayout"
+                        android:background="@color/color_primary_light"
+                        android:visibility="gone">
+
+                        <EditText
+                            android:id="@+id/ring_password"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginBottom="8dp"
+                            android:layout_marginLeft="12dp"
+                            android:layout_marginRight="12dp"
+                            android:hint="@string/prompt_new_password"
+                            android:inputType="textPassword"
+                            android:imeOptions="actionNext" />
+
+                        <EditText
+                            android:id="@+id/ring_password_repeat"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginBottom="8dp"
+                            android:layout_marginLeft="12dp"
+                            android:layout_marginRight="12dp"
+                            android:hint="@string/prompt_new_password_repeat"
+                            android:imeActionId="@integer/register_sip_account_actionid"
+                            android:imeActionLabel="@string/action_create_short"
+                            android:inputType="textPassword" />
+                    </LinearLayout>
+
+                    <Button
+                        android:id="@+id/ring_create_btn"
+                        style="?attr/borderlessButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:background="?android:attr/selectableItemBackground"
+                        android:text="Create new account"
+                        android:textColor="@color/text_color_primary_dark" />
+                </LinearLayout>
 
             </RelativeLayout>
 
@@ -164,14 +196,15 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center"
             android:layout_marginBottom="8dp"
+            android:animateLayoutChanges="false"
             android:clickable="true"
-            android:foreground="?android:attr/selectableItemBackground"
-            card_view:cardCornerRadius="2dp">
+            android:foreground="?android:attr/selectableItemBackground">
 
             <RelativeLayout
                 android:id="@+id/sip_fields"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
+                android:animateLayoutChanges="true"
                 android:nestedScrollingEnabled="false"
                 android:orientation="vertical">
 
@@ -296,6 +329,6 @@
 
         </android.support.v7.widget.CardView>
 
-    </LinearLayout>
+    </cx.ring.views.BoundedLinearLayout>
 
 </ScrollView>
diff --git a/ring-android/app/src/main/res/layout/frag_accounts_list.xml b/ring-android/app/src/main/res/layout/frag_accounts_list.xml
index 6cd6910..e04047f 100644
--- a/ring-android/app/src/main/res/layout/frag_accounts_list.xml
+++ b/ring-android/app/src/main/res/layout/frag_accounts_list.xml
@@ -1,4 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2016 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, see <http://www.gnu.org/licenses/>.
+-->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:dslv="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
diff --git a/ring-android/app/src/main/res/layout/item_account.xml b/ring-android/app/src/main/res/layout/item_account.xml
index 41a03af..33cacc5 100644
--- a/ring-android/app/src/main/res/layout/item_account.xml
+++ b/ring-android/app/src/main/res/layout/item_account.xml
@@ -1,4 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2016 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, see <http://www.gnu.org/licenses/>.
+-->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/ring-android/app/src/main/res/values/attrs.xml b/ring-android/app/src/main/res/values/attrs.xml
index db3e679..fe95528 100644
--- a/ring-android/app/src/main/res/values/attrs.xml
+++ b/ring-android/app/src/main/res/values/attrs.xml
@@ -72,5 +72,9 @@
         <attr name="use_default_controller" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="BoundedView">
+        <attr name="bounded_width" format="dimension" />
+        <attr name="bounded_height" format="dimension" />
+    </declare-styleable>
 
 </resources>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index eb67a96..e762524 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -164,5 +164,7 @@
 
     <!-- Contacts -->
     <string name="add_call_contact_number_to_contacts">Add %1$s ?</string>
+    <string name="prompt_new_password">New password</string>
+    <string name="prompt_new_password_repeat">Repeat new password</string>
 
 </resources>