preferences: allow to change account password

Gitlab: #534
Change-Id: Ibec8aedba8c52cec0075a6455aa8bae7b6717788
Reviewed-by: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
diff --git a/ring-android/app/src/main/java/cx/ring/account/ChangePasswordDialog.java b/ring-android/app/src/main/java/cx/ring/account/ChangePasswordDialog.java
new file mode 100644
index 0000000..89aa6f0
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/ChangePasswordDialog.java
@@ -0,0 +1,146 @@
+/*
+ *  Copyright (C) 2004-2019 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/>.
+ */
+package cx.ring.account;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.google.android.material.textfield.TextInputLayout;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import butterknife.BindString;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnEditorAction;
+import cx.ring.R;
+
+public class ChangePasswordDialog extends DialogFragment {
+    static final String TAG = ChangePasswordDialog.class.getSimpleName();
+
+    @BindView(R.id.old_password_txt_box)
+    protected TextInputLayout mPasswordTxtBox;
+
+    @BindView(R.id.old_password_txt)
+    protected EditText mPasswordTxt;
+
+    @BindView(R.id.new_password_txt_box)
+    protected TextInputLayout mNewPasswordTxtBox;
+
+    @BindView(R.id.new_password_txt)
+    protected EditText mNewPasswordTxt;
+
+    @BindView(R.id.new_password_repeat_txt_box)
+    protected TextInputLayout mNewPasswordRepeatsTxtBox;
+
+    @BindView(R.id.new_password_repeat_txt)
+    protected EditText mNewPasswordRepeatsTxt;
+
+    @BindString(R.string.enter_password)
+    protected String mPromptPassword;
+
+    private PasswordChangedListener mListener = null;
+
+    public void setListener(PasswordChangedListener listener) {
+        mListener = listener;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_set_password, null);
+        ButterKnife.bind(this, view);
+
+        String accountId = "";
+        boolean hasPassword = true;
+        Bundle args = getArguments();
+        if (args != null) {
+            accountId = args.getString(AccountEditionActivity.ACCOUNT_ID_KEY, accountId);
+            hasPassword = args.getBoolean(AccountEditionActivity.ACCOUNT_HAS_PASSWORD_KEY, true);
+        }
+        int passwordMessage = hasPassword ? R.string.account_password_change : R.string.account_password_set;
+        mPasswordTxtBox.setVisibility(hasPassword ? View.VISIBLE : View.GONE);
+
+        final AlertDialog result = new AlertDialog.Builder(requireContext())
+                .setView(view)
+                .setMessage(R.string.help_password_choose)
+                .setTitle(passwordMessage)
+                .setPositiveButton(passwordMessage, null) //Set to null. We override the onclick
+                .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> dismiss())
+                .create();
+        result.setOnShowListener(dialog -> {
+            Button positiveButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
+            positiveButton.setOnClickListener(view1 -> {
+                if (validate()) {
+                    dismiss();
+                }
+            });
+        });
+        result.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+        return result;
+    }
+
+    public boolean checkInput() {
+        if (!mNewPasswordTxt.getText().toString().contentEquals(mNewPasswordRepeatsTxt.getText())) {
+            mNewPasswordTxtBox.setErrorEnabled(true);
+            mNewPasswordTxtBox.setError(getText(R.string.error_passwords_not_equals));
+            mNewPasswordRepeatsTxtBox.setErrorEnabled(true);
+            mNewPasswordRepeatsTxtBox.setError(getText(R.string.error_passwords_not_equals));
+            return false;
+        } else {
+            mNewPasswordTxtBox.setErrorEnabled(false);
+            mNewPasswordTxtBox.setError(null);
+            mNewPasswordRepeatsTxtBox.setErrorEnabled(false);
+            mNewPasswordRepeatsTxtBox.setError(null);
+        }
+        return true;
+    }
+
+    private boolean validate() {
+        if (checkInput() && mListener != null) {
+            final String oldPassword = mPasswordTxt.getText().toString();
+            final String newPassword = mNewPasswordTxt.getText().toString();
+            mListener.onPasswordChanged(oldPassword, newPassword);
+            return true;
+        }
+        return false;
+    }
+
+    @OnEditorAction({R.id.new_password_repeat_txt})
+    public boolean onEditorAction(TextView v, int actionId) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            if (validate()) {
+                getDialog().dismiss();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public interface PasswordChangedListener {
+        void onPasswordChanged(String oldPassword, String newPassword);
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/account/ConfirmRevocationDialog.java b/ring-android/app/src/main/java/cx/ring/account/ConfirmRevocationDialog.java
index 759dbce..42377cd 100644
--- a/ring-android/app/src/main/java/cx/ring/account/ConfirmRevocationDialog.java
+++ b/ring-android/app/src/main/java/cx/ring/account/ConfirmRevocationDialog.java
@@ -22,6 +22,8 @@
 import android.app.Dialog;
 import android.os.Bundle;
 import com.google.android.material.textfield.TextInputLayout;
+
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import android.view.View;
 import android.view.WindowManager;
@@ -55,14 +57,15 @@
         mListener = listener;
     }
 
+    @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_confirm_revocation, null);
+        View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_confirm_revocation, null);
         ButterKnife.bind(this, view);
 
         mDeviceId = getArguments().getString(DEVICEID_KEY);
 
-        final AlertDialog result = new AlertDialog.Builder(getActivity())
+        final AlertDialog result = new AlertDialog.Builder(requireContext())
                 .setView(view)
                 .setMessage(getString(R.string.revoke_device_message, mDeviceId))
                 .setTitle(mRegisterTitle)
@@ -83,7 +86,7 @@
         return result;
     }
 
-    public boolean checkInput() {
+    private boolean checkInput() {
         if (mPasswordTxt.getText().toString().isEmpty()) {
             mPasswordTxtBox.setErrorEnabled(true);
             mPasswordTxtBox.setError(mPromptPassword);
@@ -95,7 +98,7 @@
         return true;
     }
 
-    boolean validate() {
+    private boolean validate() {
         if (checkInput() && mListener != null) {
             final String password = mPasswordTxt.getText().toString();
             mListener.onConfirmRevocation(mDeviceId, password);
diff --git a/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java b/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
index 570b6df..ee66aec 100644
--- a/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
+++ b/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
@@ -34,6 +34,7 @@
 
 import javax.inject.Inject;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import butterknife.BindString;
@@ -113,9 +114,10 @@
         }
     }
 
+    @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+        final AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
 
         View view = getActivity().getLayoutInflater().inflate(R.layout.frag_register_name, null);
 
diff --git a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
index f748bd0..6b8e296 100644
--- a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
+++ b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
@@ -18,7 +18,6 @@
  */
 package cx.ring.account;
 
-import android.app.Activity;
 import android.app.Dialog;
 import android.os.Bundle;
 import com.google.android.material.textfield.TextInputLayout;
@@ -57,13 +56,12 @@
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Activity activity = getActivity();
-        View view = activity.getLayoutInflater().inflate(R.layout.dialog_device_rename, null);
+        View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_device_rename, null);
         ButterKnife.bind(this, view);
 
         mDeviceNameTxt.setText(getArguments().getString(DEVICENAME_KEY));
 
-        final AlertDialog dialog = new AlertDialog.Builder(activity)
+        final AlertDialog dialog = new AlertDialog.Builder(requireContext())
                 .setView(view)
                 .setTitle(R.string.rename_device_title)
                 .setMessage(R.string.rename_device_message)
diff --git a/ring-android/app/src/main/java/cx/ring/account/RingAccountSummaryFragment.java b/ring-android/app/src/main/java/cx/ring/account/RingAccountSummaryFragment.java
index 6afed88..c7d083f 100644
--- a/ring-android/app/src/main/java/cx/ring/account/RingAccountSummaryFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/account/RingAccountSummaryFragment.java
@@ -22,14 +22,12 @@
 
 import android.app.ProgressDialog;
 import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 
+import com.google.android.material.chip.Chip;
 import com.google.android.material.textfield.TextInputLayout;
 
 import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.widget.SwitchCompat;
 
@@ -58,7 +56,6 @@
 import cx.ring.dependencyinjection.RingInjectionComponent;
 import cx.ring.interfaces.BackHandlerInterface;
 import cx.ring.model.Account;
-import cx.ring.model.ConfigKey;
 import cx.ring.mvp.BaseSupportFragment;
 import cx.ring.utils.KeyboardVisibilityManager;
 import cx.ring.views.LinkNewDeviceLayout;
@@ -68,10 +65,12 @@
         RenameDeviceDialog.RenameDeviceListener,
         DeviceAdapter.DeviceRevocationListener,
         ConfirmRevocationDialog.ConfirmRevocationListener,
-        RingAccountSummaryView {
+        RingAccountSummaryView, ChangePasswordDialog.PasswordChangedListener {
 
     public static final String TAG = RingAccountSummaryFragment.class.getSimpleName();
-    private static final String FRAGMENT_DIALOG_REVOCATION = RingAccountSummaryFragment.class.getSimpleName() + ".dialog.deviceRevocation";
+    private static final String FRAGMENT_DIALOG_REVOCATION = TAG + ".dialog.deviceRevocation";
+    private static final String FRAGMENT_DIALOG_RENAME = TAG + ".dialog.deviceRename";
+    private static final String FRAGMENT_DIALOG_PASSWORD = TAG + ".dialog.changePassword";
 
     /*
     UI Bindings
@@ -97,6 +96,9 @@
     @BindView(R.id.account_id_txt)
     TextView mAccountIdTxt;
 
+    @BindView(R.id.change_password_btn)
+    Button mChangePasswordBtn;
+
     @BindView(R.id.registered_name_txt)
     TextView mAccountUsernameTxt;
 
@@ -122,7 +124,7 @@
     SwitchCompat mAccountSwitch;
 
     @BindView(R.id.account_status)
-    TextView mAccountStatus;
+    Chip mAccountStatus;
 
     /*
     Declarations
@@ -145,14 +147,14 @@
     }
 
     @Override
-    public void accountChanged(final Account account) {
-        if (account == null) {
-            Log.w(TAG, "No account to display!");
-            return;
+    public void accountChanged(@NonNull final Account account) {
+        if (mDeviceAdapter == null) {
+            mDeviceAdapter = new DeviceAdapter(requireContext(), account.getDevices(), account.getDeviceId(),
+                    RingAccountSummaryFragment.this);
+            mDeviceList.setAdapter(mDeviceAdapter);
+        } else {
+            mDeviceAdapter.setData(account.getDevices(), account.getDeviceId());
         }
-        mDeviceAdapter = new DeviceAdapter(getActivity(), account.getDevices(), account.getDeviceId(),
-                RingAccountSummaryFragment.this);
-        mDeviceList.setAdapter(mDeviceAdapter);
 
         int totalHeight = 0;
         for (int i = 0; i < mDeviceAdapter.getCount(); i++) {
@@ -166,6 +168,8 @@
         mDeviceList.setLayoutParams(par);
         mDeviceList.requestLayout();
 
+        mChangePasswordBtn.setText(account.hasPassword() ? R.string.account_password_change : R.string.account_password_set);
+
         mAccountSwitch.setChecked(account.isEnabled());
         mAccountNameTxt.setText(account.getAlias());
         mAccountIdTxt.setText(account.getUsername());
@@ -179,11 +183,12 @@
             mAccountUsernameTxt.setText(username);
         }
 
-        int color = ContextCompat.getColor(getContext(), R.color.holo_red_light);
+        int color = R.color.red_400;
         String status;
 
         if (account.isEnabled()) {
             if (account.isTrying()) {
+                color = R.color.orange_400;
                 status = getString(R.string.account_status_connecting);
             } else if (account.needsMigration()) {
                 status = getString(R.string.account_update_needed);
@@ -191,21 +196,19 @@
                 status = getString(R.string.account_status_connection_error);
             } else if (account.isRegistered()) {
                 status = getString(R.string.account_status_online);
-                color = ContextCompat.getColor(getContext(), R.color.holo_green_dark);
+                color = R.color.green_400;
             } else {
                 status = getString(R.string.account_status_unknown);
             }
         } else {
-            color = ContextCompat.getColor(getContext(), R.color.darker_gray);
+            color = R.color.grey_400;
             status = getString(R.string.account_status_offline);
         }
 
         mAccountStatus.setText(status);
-        Drawable wrapped = DrawableCompat.wrap(getContext().getDrawable(R.drawable.static_rounded_background));
-        DrawableCompat.setTint(wrapped, color);
-        mAccountStatus.setBackground(wrapped);
+        mAccountStatus.setChipBackgroundColorResource(color);
 
-        mAccountHasPassword = account.getDetailBoolean(ConfigKey.ARCHIVE_HAS_PASSWORD);
+        mAccountHasPassword = account.hasPassword();
         mPasswordLayout.setVisibility(mAccountHasPassword ? View.VISIBLE : View.GONE);
     }
 
@@ -254,8 +257,8 @@
 
     @Override
     public void showNetworkError() {
-        mWaitDialog.dismiss();
-        new AlertDialog.Builder(getActivity())
+        dismissWaitDialog();
+        new AlertDialog.Builder(requireActivity())
                 .setTitle(R.string.account_export_end_network_title)
                 .setMessage(R.string.account_export_end_network_message)
                 .setPositiveButton(android.R.string.ok, null)
@@ -264,15 +267,15 @@
 
     @Override
     public void showPasswordError() {
-        mWaitDialog.dismiss();
+        dismissWaitDialog();
         mPasswordLayout.setError(getString(R.string.account_export_end_decryption_message));
         mRingPassword.setText("");
     }
 
     @Override
     public void showGenericError() {
-        mWaitDialog.dismiss();
-        new AlertDialog.Builder(getActivity())
+        dismissWaitDialog();
+        new AlertDialog.Builder(requireActivity())
                 .setTitle(R.string.account_export_end_error_title)
                 .setMessage(R.string.account_export_end_error_message)
                 .setPositiveButton(android.R.string.ok, null)
@@ -284,8 +287,7 @@
     }
 
     @OnEditorAction(R.id.ring_password)
-    @SuppressWarnings("unused")
-    public boolean onPasswordEditorAction(TextView pwd, int actionId, KeyEvent event) {
+    boolean onPasswordEditorAction(TextView pwd, int actionId, KeyEvent event) {
         Log.i(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
         if (actionId == EditorInfo.IME_ACTION_DONE) {
             if (pwd.getText().length() == 0) {
@@ -299,26 +301,26 @@
     }
 
     @OnClick(R.id.btn_start_export)
-    public void onClickStart() {
+    void onClickStart() {
         mPasswordLayout.setError(null);
         String password = mRingPassword.getText().toString();
         presenter.startAccountExport(password);
     }
 
     @OnClick(R.id.account_switch)
-    public void onToggleAccount() {
+    void onToggleAccount() {
         presenter.enableAccount(mAccountSwitch.isChecked());
     }
 
     @OnClick(R.id.register_name_btn)
-    public void showUsernameRegistrationPopup() {
+    void showUsernameRegistrationPopup() {
         Bundle args = new Bundle();
         args.putString(AccountEditionActivity.ACCOUNT_ID_KEY, getArguments().getString(AccountEditionActivity.ACCOUNT_ID_KEY));
         args.putBoolean(AccountEditionActivity.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword);
         RegisterNameDialog registrationDialog = new RegisterNameDialog();
         registrationDialog.setArguments(args);
         registrationDialog.setListener(this);
-        registrationDialog.show(getFragmentManager(), TAG);
+        registrationDialog.show(requireFragmentManager(), TAG);
     }
 
     @Override
@@ -341,8 +343,15 @@
     }
 
     @Override
+    public void showPasswordProgressDialog() {
+        mWaitDialog = ProgressDialog.show(getActivity(),
+                getString(R.string.export_account_wait_title),
+                getString(R.string.account_password_change_wait_message));
+    }
+
+    @Override
     public int getLayout() {
-        return R.layout.frag_device_list;
+        return R.layout.frag_acc_summary;
     }
 
     @Override
@@ -350,15 +359,22 @@
         component.inject(this);
     }
 
+    private void dismissWaitDialog() {
+        if (mWaitDialog != null) {
+            mWaitDialog.dismiss();
+            mWaitDialog = null;
+        }
+    }
+
+
     @Override
     public void showPIN(final String pin) {
         hideWizard();
-        mWaitDialog.dismiss();
         mLinkAccountView.setVisibility(View.VISIBLE);
         mPasswordLayout.setVisibility(View.GONE);
         mEndBtn.setVisibility(View.VISIBLE);
         mStartBtn.setVisibility(View.GONE);
-
+        dismissWaitDialog();
         String pined = getString(R.string.account_end_export_infos).replace("%%", pin);
         final SpannableString styledResultText = new SpannableString(pined);
         int pos = pined.lastIndexOf(pin);
@@ -380,8 +396,20 @@
     }
 
     @Override
+    public void passwordChangeEnded(boolean ok) {
+        dismissWaitDialog();
+        if (!ok) {
+            new AlertDialog.Builder(requireActivity())
+                    .setTitle(R.string.account_device_revocation_wrong_password)
+                    .setMessage(R.string.account_export_end_decryption_message)
+                    .setPositiveButton(android.R.string.ok, null)
+                    .show();
+        }
+    }
+
+    @Override
     public void deviceRevocationEnded(final String device, final int status) {
-        mWaitDialog.dismiss();
+        dismissWaitDialog();
         int message, title = R.string.account_device_revocation_error_title;
         switch (status) {
             case 0:
@@ -397,7 +425,7 @@
             default:
                 message = R.string.account_device_revocation_error_unknown;
         }
-        new AlertDialog.Builder(getActivity())
+        new AlertDialog.Builder(requireActivity())
                 .setTitle(title)
                 .setMessage(message)
                 .setPositiveButton(android.R.string.ok, (dialog, which) -> {
@@ -421,7 +449,7 @@
         args.putString(ConfirmRevocationDialog.DEVICEID_KEY, deviceId);
         dialog.setArguments(args);
         dialog.setListener(this);
-        dialog.show(getFragmentManager(), FRAGMENT_DIALOG_REVOCATION);
+        dialog.show(requireFragmentManager(), FRAGMENT_DIALOG_REVOCATION);
     }
 
     @Override
@@ -432,7 +460,18 @@
         args.putString(RenameDeviceDialog.DEVICENAME_KEY, dev_name);
         dialog.setArguments(args);
         dialog.setListener(this);
-        dialog.show(getFragmentManager(), TAG);
+        dialog.show(requireFragmentManager(), FRAGMENT_DIALOG_RENAME);
+    }
+
+    @OnClick(R.id.change_password_btn)
+    public void onPasswordChangeAsked() {
+        ChangePasswordDialog dialog = new ChangePasswordDialog();
+        Bundle args = new Bundle();
+        args.putString(AccountEditionActivity.ACCOUNT_ID_KEY, getArguments().getString(AccountEditionActivity.ACCOUNT_ID_KEY));
+        args.putBoolean(AccountEditionActivity.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword);
+        dialog.setArguments(args);
+        dialog.setListener(this);
+        dialog.show(requireFragmentManager(), FRAGMENT_DIALOG_PASSWORD);
     }
 
     @Override
@@ -440,4 +479,9 @@
         Log.d(TAG, "onDeviceRename: " + presenter.getDeviceName() + " -> " + newName);
         presenter.renameDevice(newName);
     }
+
+    @Override
+    public void onPasswordChanged(String oldPassword, String newPassword) {
+        presenter.changePassword(oldPassword, newPassword);
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
index e4fa66e..0069281 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
@@ -106,6 +106,11 @@
     }
 
     @Override
+    public void showPasswordProgressDialog() {
+
+    }
+
+    @Override
     public void accountChanged(Account account) {
 
     }
@@ -165,4 +170,9 @@
     public void deviceRevocationEnded(String device, int status) {
 
     }
+
+    @Override
+    public void passwordChangeEnded(boolean ok) {
+
+    }
 }
diff --git a/ring-android/app/src/main/res/layout/add_new_device_layout.xml b/ring-android/app/src/main/res/layout/add_new_device_layout.xml
index de24102..34fb9ea 100644
--- a/ring-android/app/src/main/res/layout/add_new_device_layout.xml
+++ b/ring-android/app/src/main/res/layout/add_new_device_layout.xml
@@ -25,7 +25,7 @@
     android:clipToPadding="false"
     android:paddingTop="20dp"
     android:theme="@style/Wizard"
-    tools:showIn="@layout/frag_device_list">
+    tools:showIn="@layout/frag_acc_summary">
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/ring-android/app/src/main/res/layout/dialog_set_password.xml b/ring-android/app/src/main/res/layout/dialog_set_password.xml
new file mode 100644
index 0000000..f0ce638
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/dialog_set_password.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:fadeScrollbars="false"
+    android:scrollIndicators="top|bottom">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:scrollbars="vertical"
+        android:scrollbarAlwaysDrawVerticalTrack="true">
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/old_password_txt_box"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="12dp"
+            android:layout_marginRight="12dp"
+            android:layout_marginBottom="16dp">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/old_password_txt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/help_password_enter"
+                android:imeOptions="actionNext"
+                android:inputType="textPassword"
+                android:lines="1"
+                android:maxLines="1" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/new_password_txt_box"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="12dp"
+            android:layout_marginRight="12dp"
+            android:layout_marginBottom="16dp">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/new_password_txt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/prompt_new_password"
+                android:imeOptions="actionNext"
+                android:inputType="textPassword"
+                android:lines="1"
+                android:maxLines="1" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/new_password_repeat_txt_box"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="12dp"
+            android:layout_marginRight="12dp"
+            android:layout_marginBottom="16dp">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/new_password_repeat_txt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/prompt_new_password_repeat"
+                android:imeOptions="actionDone"
+                android:inputType="textPassword"
+                android:lines="1"
+                android:maxLines="1" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_device_list.xml b/ring-android/app/src/main/res/layout/frag_acc_summary.xml
similarity index 77%
rename from ring-android/app/src/main/res/layout/frag_device_list.xml
rename to ring-android/app/src/main/res/layout/frag_acc_summary.xml
index 59093f1..ca8d04e 100644
--- a/ring-android/app/src/main/res/layout/frag_device_list.xml
+++ b/ring-android/app/src/main/res/layout/frag_acc_summary.xml
@@ -46,20 +46,17 @@
                     android:layout_height="wrap_content"
                     android:layout_toStartOf="@+id/account_switch"
                     android:paddingBottom="8dp"
-                    android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
+                    android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
                     tools:text="@string/ring_account" />
 
-                <TextView
+                <com.google.android.material.chip.Chip
                     android:id="@+id/account_status"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_below="@id/account_alias_txt"
-                    android:textAppearance="@style/Base.TextAppearance.AppCompat.Small.Inverse"
                     android:textColor="@color/white"
-                    android:textStyle="bold"
                     tools:text="Registered" />
 
-
                 <androidx.appcompat.widget.SwitchCompat
                     android:id="@+id/account_switch"
                     android:layout_width="50dp"
@@ -73,58 +70,75 @@
                 android:layout_height="match_parent"
                 android:layout_below="@+id/ring_account_status_container"
                 android:orientation="vertical"
-                android:paddingBottom="8dp"
-                android:paddingEnd="16dp"
                 android:paddingStart="16dp"
-                android:paddingTop="24dp">
+                android:paddingTop="12dp"
+                android:paddingEnd="16dp"
+                android:paddingBottom="8dp">
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dip"
+                    android:layout_marginVertical="16dp"
+                    android:background="?android:attr/listDivider"/>
 
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/ring_account_identity"
-                    android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" />
+                    android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" />
 
                 <TextView
                     android:id="@+id/account_id_txt"
-                    style="@style/Subheader"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:ellipsize="middle"
-                    android:gravity="center_vertical"
-                    android:paddingBottom="8dp"
-                    android:paddingLeft="16dp"
-                    android:paddingRight="16dp"
-                    android:paddingTop="8dp"
                     android:singleLine="true"
                     android:textIsSelectable="true"
-                    android:textStyle="bold"
+                    android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
                     tools:text="ring:8F29045378ACA68F2ACA2346078ACA68F2ACA290" />
 
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/change_password_btn"
+                    style="@style/Widget.MaterialComponents.Button.OutlinedButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="end"
+                    android:paddingStart="16dp"
+                    android:paddingEnd="16dp"
+                    android:text="@string/account_password_change" />
+
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dip"
+                    android:layout_marginVertical="16dp"
+                    android:background="?android:attr/listDivider" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/registered_username"
+                    android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" />
+
                 <LinearLayout
                     android:id="@+id/group_register_name"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_margin="16dp"
-                    android:gravity="center_horizontal"
                     android:orientation="vertical">
 
                     <TextView
-                        style="@style/Subheader"
-                        android:layout_width="match_parent"
+                        android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:gravity="center_vertical"
-                        android:paddingBottom="8dp"
-                        android:paddingTop="8dp"
-                        android:text="@string/no_registered_name_for_account"
-                        android:textAlignment="center" />
+                        android:text="@string/no_registered_name_for_account" />
 
                     <com.google.android.material.button.MaterialButton
                         android:id="@+id/register_name_btn"
                         style="@style/Widget.MaterialComponents.Button"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:paddingEnd="16dp"
+                        android:layout_gravity="end"
                         android:paddingStart="16dp"
+                        android:paddingEnd="16dp"
                         android:text="@string/register_name" />
 
                 </LinearLayout>
@@ -133,9 +147,7 @@
                     android:id="@+id/group_registering_name"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"
-                    android:layout_margin="16dp"
-                    android:orientation="horizontal"
-                    android:visibility="gone">
+                    android:orientation="horizontal">
 
                     <TextView
                         android:id="@+id/textView"
@@ -155,51 +167,34 @@
                     android:id="@+id/group_registered_name"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_margin="16dp"
-                    android:orientation="horizontal"
-                    android:visibility="gone">
-
-                    <TextView
-                        style="@style/Subheader"
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:gravity="center_vertical"
-                        android:paddingBottom="16dp"
-                        android:paddingTop="4dp"
-                        android:text="@string/registered_username" />
+                    android:orientation="horizontal">
 
                     <TextView
                         android:id="@+id/registered_name_txt"
-                        style="@style/Base.TextAppearance.AppCompat.Title"
+                        android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_gravity="start"
-                        android:paddingBottom="16dp"
-                        android:paddingEnd="8dp"
-                        android:paddingStart="16dp"
-                        android:paddingTop="4dp"
                         android:textAlignment="viewStart"
                         android:textIsSelectable="true"
                         tools:text="blockchain_name" />
 
                 </LinearLayout>
 
-                <TextView
-                    style="@style/Subheader"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:gravity="center_vertical"
-                    android:paddingBottom="8dp"
-                    android:paddingEnd="16dp"
-                    android:paddingStart="16dp"
-                    android:paddingTop="8dp"
-                    android:text="@string/normal_devices_titles" />
 
                 <View
                     android:layout_width="match_parent"
                     android:layout_height="1dip"
+                    android:layout_marginVertical="16dp"
                     android:background="?android:attr/listDivider" />
 
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical"
+                    android:text="@string/normal_devices_titles"
+                    android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" />
+
                 <FrameLayout
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
@@ -208,7 +203,7 @@
                         android:id="@+id/device_list"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
-                        android:divider="#CCCCCC"
+                        android:divider="#DDDDDD"
                         android:dividerHeight="1dp"
                         android:footerDividersEnabled="true"
                         android:headerDividersEnabled="true" />
diff --git a/ring-android/app/src/main/res/layout/item_device.xml b/ring-android/app/src/main/res/layout/item_device.xml
index 03785a4..aa7c566 100644
--- a/ring-android/app/src/main/res/layout/item_device.xml
+++ b/ring-android/app/src/main/res/layout/item_device.xml
@@ -72,6 +72,7 @@
         android:layout_height="wrap_content"
         android:layout_alignWithParentIfMissing="true"
         android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
         android:layout_toStartOf="@+id/revoke_button"
         android:background="?selectableItemBackgroundBorderless"
         android:contentDescription="@string/account_device_revoke"
@@ -85,6 +86,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentEnd="true"
         android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
         android:background="?selectableItemBackgroundBorderless"
         android:contentDescription="@string/account_device_revoke"
         android:padding="8dp"
diff --git a/ring-android/app/src/main/res/values/strings_account.xml b/ring-android/app/src/main/res/values/strings_account.xml
index f0f9262..7a8d12f 100644
--- a/ring-android/app/src/main/res/values/strings_account.xml
+++ b/ring-android/app/src/main/res/values/strings_account.xml
@@ -72,6 +72,9 @@
     <string name="account_device_revocation_wrong_password">Wrong password.</string>
     <string name="account_device_revocation_unknown_device">Unknown device.</string>
     <string name="account_device_revocation_error_unknown">Unknown error.</string>
+    <string name="account_password_change">Change password</string>
+    <string name="account_password_set">Set password</string>
+    <string name="account_password_change_wait_message">Changing account password</string>
 
     <!-- Basic Details -->
     <string name="account_preferences_basic_tab">General</string>
diff --git a/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryPresenter.java
index b83311d..3739e56 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryPresenter.java
@@ -88,7 +88,6 @@
                     RingAccountSummaryView view = getView();
                     if (view != null) {
                         view.accountChanged(account);
-                        view.updateDeviceList(account.getDevices(), account.getDeviceId());
                     }
                 }));
     }
@@ -119,6 +118,17 @@
         mAccountService.renameDevice(mAccountID, newName);
     }
 
+    public void changePassword(String oldPassword, String newPassword) {
+        RingAccountSummaryView view = getView();
+        if (view != null)
+            view.showPasswordProgressDialog();
+        mCompositeDisposable.add(mAccountService.setAccountPassword(mAccountID, oldPassword, newPassword)
+                .observeOn(mUiScheduler)
+                .subscribe(
+                        () -> getView().passwordChangeEnded(true),
+                        e -> getView().passwordChangeEnded(false)));
+    }
+
     public String getDeviceName() {
         Account account = mAccountService.getAccount(mAccountID);
         if (account == null) {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryView.java b/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryView.java
index 413f0b0..c4047f0 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryView.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/account/RingAccountSummaryView.java
@@ -29,6 +29,8 @@
 
     void showRevokingProgressDialog();
 
+    void showPasswordProgressDialog();
+
     void accountChanged(final Account account);
 
     void showNetworkError();
@@ -42,5 +44,6 @@
     void updateDeviceList(Map<String, String> devices, String currentDeviceId);
 
     void deviceRevocationEnded(String device, int status);
+    void passwordChangeEnded(boolean ok);
 
 }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Account.java b/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
index ffcdc4d..5cf4d59 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
@@ -396,6 +396,10 @@
         mDetails.put(ConfigKey.ACCOUNT_ENABLE, isChecked);
     }
 
+    public boolean hasPassword() {
+        return mDetails.getBool(ConfigKey.ARCHIVE_HAS_PASSWORD);
+    }
+
     public HashMap<String, String> getDetails() {
         return mDetails.getAll();
     }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java b/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
index 6619e51..6947699 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/AccountService.java
@@ -58,6 +58,7 @@
 import cx.ring.utils.SwigNativeConverter;
 import cx.ring.utils.VCardUtils;
 import ezvcard.VCard;
+import io.reactivex.Completable;
 import io.reactivex.Single;
 import io.reactivex.schedulers.Schedulers;
 import io.reactivex.subjects.BehaviorSubject;
@@ -672,6 +673,17 @@
     }
 
     /**
+     * @param accountId id of the account
+     * @param oldPassword   old account password
+     */
+    public Completable setAccountPassword(final String accountId, final String oldPassword, final String newPassword) {
+        return Completable.fromAction(() -> {
+            if (!Ringservice.changeAccountPassword(accountId, oldPassword, newPassword))
+                throw new IllegalArgumentException("Can't change password");
+        }).subscribeOn(Schedulers.from(mExecutor));
+    }
+
+    /**
      * Sets the active codecs list of the account in the Daemon
      */
     public void setActiveCodecList(final String accountId, final List<Long> codecs) {