Add ability to change password on server

Sam Whited created

Fixes #260

Change summary

src/main/java/eu/siacs/conversations/entities/Account.java               |  45 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  12 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  37 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        |   2 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java         | 112 
src/main/java/eu/siacs/conversations/utils/Xmlns.java                    |   1 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            |   4 
src/main/res/layout/activity_edit_account.xml                            |  11 
src/main/res/values/strings.xml                                          |   3 
9 files changed, 139 insertions(+), 88 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Account.java 🔗

@@ -10,6 +10,7 @@ import net.java.otr4j.crypto.OtrCryptoException;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.security.PublicKey;
 import java.security.interfaces.DSAPublicKey;
 import java.util.Collection;
 import java.util.List;
@@ -152,7 +153,7 @@ public class Account extends AbstractEntity {
 		this.avatar = avatar;
 	}
 
-	public static Account fromCursor(Cursor cursor) {
+	public static Account fromCursor(final Cursor cursor) {
 		Jid jid = null;
 		try {
 			jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
@@ -168,11 +169,11 @@ public class Account extends AbstractEntity {
 				cursor.getString(cursor.getColumnIndex(AVATAR)));
 	}
 
-	public boolean isOptionSet(int option) {
+	public boolean isOptionSet(final int option) {
 		return ((options & (1 << option)) != 0);
 	}
 
-	public void setOption(int option, boolean value) {
+	public void setOption(final int option, final boolean value) {
 		if (value) {
 			this.options |= 1 << option;
 		} else {
@@ -243,34 +244,18 @@ public class Account extends AbstractEntity {
 		return keys;
 	}
 
-	public String getSSLFingerprint() {
-		if (keys.has("ssl_cert")) {
-			try {
-				return keys.getString("ssl_cert");
-			} catch (JSONException e) {
-				return null;
-			}
-		} else {
-			return null;
-		}
-	}
-
-	public void setSSLCertFingerprint(String fingerprint) {
-		this.setKey("ssl_cert", fingerprint);
-	}
-
-	public boolean setKey(String keyName, String keyValue) {
+	public boolean setKey(final String keyName, final String keyValue) {
 		try {
 			this.keys.put(keyName, keyValue);
 			return true;
-		} catch (JSONException e) {
+		} catch (final JSONException e) {
 			return false;
 		}
 	}
 
 	@Override
 	public ContentValues getContentValues() {
-		ContentValues values = new ContentValues();
+		final ContentValues values = new ContentValues();
 		values.put(UUID, uuid);
 		values.put(USERNAME, jid.getLocalpart());
 		values.put(SERVER, jid.getDomainpart());
@@ -304,8 +289,8 @@ public class Account extends AbstractEntity {
 				if (this.otrEngine == null) {
 					return null;
 				}
-				DSAPublicKey publicKey = (DSAPublicKey) this.otrEngine.getPublicKey();
-				if (publicKey == null) {
+				final PublicKey publicKey = this.otrEngine.getPublicKey();
+				if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
 					return null;
 				}
 				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
@@ -338,7 +323,7 @@ public class Account extends AbstractEntity {
 		if (keys.has("pgp_signature")) {
 			try {
 				return keys.getString("pgp_signature");
-			} catch (JSONException e) {
+			} catch (final JSONException e) {
 				return null;
 			}
 		} else {
@@ -359,7 +344,7 @@ public class Account extends AbstractEntity {
 	}
 
 	public boolean hasBookmarkFor(final Jid conferenceJid) {
-		for (Bookmark bookmark : this.bookmarks) {
+		for (final Bookmark bookmark : this.bookmarks) {
 			final Jid jid = bookmark.getJid();
 			if (jid != null && jid.equals(conferenceJid.toBareJid())) {
 				return true;
@@ -368,7 +353,7 @@ public class Account extends AbstractEntity {
 		return false;
 	}
 
-	public boolean setAvatar(String filename) {
+	public boolean setAvatar(final String filename) {
 		if (this.avatar != null && this.avatar.equals(filename)) {
 			return false;
 		} else {
@@ -395,7 +380,7 @@ public class Account extends AbstractEntity {
 	}
 
 	public String getShareableUri() {
-		String fingerprint = this.getOtrFingerprint();
+		final String fingerprint = this.getOtrFingerprint();
 		if (fingerprint != null) {
 			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
 		} else {
@@ -419,4 +404,8 @@ public class Account extends AbstractEntity {
 	public void clearBlocklist() {
 		getBlocklist().clear();
 	}
+
+	public boolean isOnlineAndConnected() {
+		return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
+	}
 }

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -4,6 +4,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.Xmlns;
@@ -117,7 +118,6 @@ public class IqGenerator extends AbstractGenerator {
 		}
 		return packet;
 	}
-
 	public IqPacket generateGetBlockList() {
 		final IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
 		iq.addChild("blocklist", Xmlns.BLOCKING);
@@ -138,4 +138,14 @@ public class IqGenerator extends AbstractGenerator {
 		block.addChild("item").setAttribute("jid", jid.toBareJid().toString());
 		return iq;
 	}
+
+	public IqPacket generateSetPassword(final Account account, final String newPassword) {
+		final IqPacket packet = new IqPacket(IqPacket.TYPE_SET);
+		packet.setTo(account.getServer());
+		final Element query = packet.addChild("query", Xmlns.REGISTER);
+		final Jid jid = account.getJid();
+		query.addChild("username").setContent(jid.isDomainJid() ? jid.toString() : jid.getLocalpart());
+		query.addChild("password").setContent(newPassword);
+		return packet;
+	}
 }

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -222,7 +222,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	private OnMucRosterUpdate mOnMucRosterUpdate = null;
 	private int mucRosterChangedListenerCount = 0;
 	private SecureRandom mRandom;
-	private FileObserver fileObserver = new FileObserver(
+	private final FileObserver fileObserver = new FileObserver(
 			FileBackend.getConversationsImageDirectory()) {
 
 		@Override
@@ -232,7 +232,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 			}
 		}
 	};
-	private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
+	private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
 
 		@Override
 		public void onJinglePacketReceived(Account account, JinglePacket packet) {
@@ -246,7 +246,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	private PendingIntent pendingPingIntent = null;
 	private WakeLock wakeLock;
 	private PowerManager pm;
-	private OnBindListener mOnBindListener = new OnBindListener() {
+	private final OnBindListener mOnBindListener = new OnBindListener() {
 
 		@Override
 		public void onBind(final Account account) {
@@ -261,7 +261,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	};
 
-	private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
+	private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
 
 		@Override
 		public void onMessageAcknowledged(Account account, String uuid) {
@@ -279,7 +279,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	};
 	private LruCache<String, Bitmap> mBitmapCache;
-	private IqGenerator mIqGenerator = new IqGenerator(this);
+	private final IqGenerator mIqGenerator = new IqGenerator(this);
 	private Thread mPhoneContactMergerThread;
 
 	public PgpEngine getPgpEngine() {
@@ -304,7 +304,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		return this.mAvatarService;
 	}
 
-	public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
+	public void attachFileToConversation(final Conversation conversation,
+			final Uri uri,
+			final UiCallback<Message> callback) {
 		final Message message;
 		if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
 			message = new Message(conversation, "",
@@ -1082,7 +1084,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	}
 
-	public void createAccount(Account account) {
+	public void createAccount(final Account account) {
 		account.initOtrEngine(this);
 		databaseBackend.createAccount(account);
 		this.accounts.add(account);
@@ -1090,7 +1092,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		updateAccountUi();
 	}
 
-	public void updateAccount(Account account) {
+	public void updateAccount(final Account account) {
 		this.statusListener.onStatusChanged(account);
 		databaseBackend.updateAccount(account);
 		reconnectAccount(account, false);
@@ -1098,9 +1100,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		getNotificationService().updateErrorNotification();
 	}
 
-	public void deleteAccount(Account account) {
+	public void updateAccountPasswordOnServer(final Account account, final String newPassword) {
+		if (account.isOnlineAndConnected()) {
+			final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword);
+			sendIqPacket(account, iq, new OnIqPacketReceived() {
+				@Override
+				public void onIqPacketReceived(final Account account, final IqPacket packet) {
+					if (packet.getType() == IqPacket.TYPE_RESULT) {
+						account.setPassword(newPassword);
+						updateAccount(account);
+					}
+				}
+			});
+		}
+	}
+
+	public void deleteAccount(final Account account) {
 		synchronized (this.conversations) {
-			for (Conversation conversation : conversations) {
+			for (final Conversation conversation : conversations) {
 				if (conversation.getAccount() == account) {
 					if (conversation.getMode() == Conversation.MODE_MULTI) {
 						leaveMuc(conversation);

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -318,7 +318,7 @@ public class ConversationActivity extends XmppActivity
 						menuUnblock.setVisible(false);
 					}
 					final Account account = this.getSelectedConversation().getAccount();
-					if (account.getStatus() != Account.State.ONLINE || !account.getXmppConnection().getFeatures().blocking()) {
+					if (account.isOnlineAndConnected() || !account.getXmppConnection().getFeatures().blocking()) {
 						menuBlock.setVisible(false);
 						menuUnblock.setVisible(false);
 					}

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -40,6 +40,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	private EditText mPassword;
 	private EditText mPasswordConfirm;
 	private CheckBox mRegisterNew;
+	private CheckBox mChangePassword;
 	private Button mCancelButton;
 	private Button mSaveButton;
 	private TableLayout mMoreTable;
@@ -63,7 +64,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 
 	private boolean mFetchingAvatar = false;
 
-	private OnClickListener mSaveButtonClickListener = new OnClickListener() {
+	private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
 
 		@Override
 		public void onClick(final View v) {
@@ -74,6 +75,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				return;
 					}
 			final boolean registerNewAccount = mRegisterNew.isChecked();
+			final boolean changePassword = mChangePassword.isChecked();
 			final Jid jid;
 			try {
 				jid = Jid.fromString(mAccountJid.getText().toString());
@@ -89,7 +91,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			}
 			final String password = mPassword.getText().toString();
 			final String passwordConfirm = mPasswordConfirm.getText().toString();
-			if (registerNewAccount) {
+			if (registerNewAccount || changePassword) {
 				if (!password.equals(passwordConfirm)) {
 					mPasswordConfirm
 						.setError(getString(R.string.passwords_do_not_match));
@@ -98,14 +100,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				}
 			}
 			if (mAccount != null) {
-				mAccount.setPassword(password);
 				try {
 					mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : "");
 					mAccount.setServer(jid.getDomainpart());
 				} catch (final InvalidJidException ignored) {
 				}
-				mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
-				xmppConnectionService.updateAccount(mAccount);
+				if (changePassword) {
+					if (mAccount.isOnlineAndConnected()) {
+						xmppConnectionService.updateAccountPasswordOnServer(mAccount, mPassword.getText().toString());
+					} else {
+						mPassword.setError(getResources().getString(R.string.account_status_no_internet));
+					}
+				} else {
+					mAccount.setPassword(password);
+					mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+					xmppConnectionService.updateAccount(mAccount);
+				}
 			} else {
 				try {
 					if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) {
@@ -114,7 +124,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 						mAccountJid.requestFocus();
 						return;
 					}
-				} catch (InvalidJidException e) {
+				} catch (final InvalidJidException e) {
 					return;
 				}
 				mAccount = new Account(jid.toBareJid(), password);
@@ -132,10 +142,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 
 		}
 	};
-	private OnClickListener mCancelButtonClickListener = new OnClickListener() {
+	private final OnClickListener mCancelButtonClickListener = new OnClickListener() {
 
 		@Override
-		public void onClick(View v) {
+		public void onClick(final View v) {
 			finish();
 		}
 	};
@@ -167,47 +177,53 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			}
 		});
 	}
-	private UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
+	private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
 
 		@Override
-		public void userInputRequried(PendingIntent pi, Avatar avatar) {
+		public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
 			finishInitialSetup(avatar);
 		}
 
 		@Override
-		public void success(Avatar avatar) {
+		public void success(final Avatar avatar) {
 			finishInitialSetup(avatar);
 		}
 
 		@Override
-		public void error(int errorCode, Avatar avatar) {
+		public void error(final int errorCode, final Avatar avatar) {
 			finishInitialSetup(avatar);
 		}
 	};
-	private TextWatcher mTextWatcher = new TextWatcher() {
+	private final TextWatcher mTextWatcher = new TextWatcher() {
 
 		@Override
-		public void onTextChanged(CharSequence s, int start, int before,
-				int count) {
+		public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
 			updateSaveButton();
 		}
 
 		@Override
-		public void beforeTextChanged(CharSequence s, int start, int count,
-				int after) {
-
+		public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
 		}
 
 		@Override
-		public void afterTextChanged(Editable s) {
-
+		public void afterTextChanged(final Editable s) {
+			final boolean registrationReady = mAccount != null &&
+				mAccount.isOnlineAndConnected() &&
+				mAccount.getXmppConnection().getFeatures().register();
+			if (jidToEdit != null && mAccount != null && registrationReady &&
+					!mAccount.getPassword().equals(s.toString()) && !"".equals(s.toString())) {
+				mChangePassword.setVisibility(View.VISIBLE);
+			} else {
+				mChangePassword.setVisibility(View.INVISIBLE);
+				mChangePassword.setChecked(false);
+			}
 		}
 	};
-	private OnClickListener mAvatarClickListener = new OnClickListener() {
+	private final OnClickListener mAvatarClickListener = new OnClickListener() {
 		@Override
-		public void onClick(View view) {
-			if (mAccount!=null) {
-				Intent intent = new Intent(getApplicationContext(),
+		public void onClick(final View view) {
+			if (mAccount != null) {
+				final Intent intent = new Intent(getApplicationContext(),
 						PublishProfilePictureActivity.class);
 				intent.putExtra("account", mAccount.getJid().toBareJid().toString());
 				startActivity(intent);
@@ -220,7 +236,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 
 			@Override
 			public void run() {
-				Intent intent;
+				final Intent intent;
 				if (avatar != null) {
 					intent = new Intent(getApplicationContext(),
 							StartConversationActivity.class);
@@ -251,8 +267,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			this.mSaveButton.setEnabled(true);
 			this.mSaveButton.setTextColor(getPrimaryTextColor());
 			if (jidToEdit != null) {
-				if (mAccount != null
-						&& mAccount.getStatus() == Account.State.ONLINE) {
+				if (mAccount != null && mAccount.isOnlineAndConnected()) {
 					this.mSaveButton.setText(R.string.save);
 					if (!accountInfoEdited()) {
 						this.mSaveButton.setEnabled(false);
@@ -268,7 +283,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	}
 
 	protected boolean accountInfoEdited() {
-		return (!this.mAccount.getJid().toBareJid().equals(
+		return (!this.mAccount.getJid().toBareJid().toString().equals(
 					this.mAccountJid.getText().toString()))
 			|| (!this.mAccount.getPassword().equals(
 						this.mPassword.getText().toString()));
@@ -284,7 +299,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	}
 
 	@Override
-	protected void onCreate(Bundle savedInstanceState) {
+	protected void onCreate(final Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
 		setContentView(R.layout.activity_edit_account);
 		this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
@@ -295,6 +310,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		this.mAvatar = (ImageView) findViewById(R.id.avater);
 		this.mAvatar.setOnClickListener(this.mAvatarClickListener);
 		this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
+		this.mChangePassword = (CheckBox) findViewById(R.id.account_change_password);
 		this.mStats = (LinearLayout) findViewById(R.id.stats);
 		this.mSessionEst = (TextView) findViewById(R.id.session_est);
 		this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version);
@@ -312,20 +328,20 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
 		this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
 		this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more);
-		this.mRegisterNew
-			.setOnCheckedChangeListener(new OnCheckedChangeListener() {
-
-				@Override
-				public void onCheckedChanged(CompoundButton buttonView,
-						boolean isChecked) {
-					if (isChecked) {
-						mPasswordConfirm.setVisibility(View.VISIBLE);
-					} else {
-						mPasswordConfirm.setVisibility(View.GONE);
-					}
-					updateSaveButton();
+		final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
+			@Override
+			public void onCheckedChanged(final CompoundButton buttonView,
+					final boolean isChecked) {
+				if (isChecked) {
+					mPasswordConfirm.setVisibility(View.VISIBLE);
+				} else {
+					mPasswordConfirm.setVisibility(View.GONE);
 				}
-			});
+				updateSaveButton();
+			}
+		};
+		this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
+		this.mChangePassword.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
 	}
 
 	@Override
@@ -340,8 +356,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			showBlocklist.setVisible(false);
 			showMoreInfo.setVisible(false);
 		} else if (mAccount.getStatus() != Account.State.ONLINE) {
-		showBlocklist.setVisible(false);
-		showMoreInfo.setVisible(false);
+			showBlocklist.setVisible(false);
+			showMoreInfo.setVisible(false);
 		} else if (!mAccount.getXmppConnection().getFeatures().blocking()) {
 			showBlocklist.setVisible(false);
 		}
@@ -368,6 +384,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 					getActionBar().setTitle(R.string.action_add_account);
 				}
 			}
+			this.mChangePassword.setVisibility(View.GONE);
+			this.mChangePassword.setChecked(false);
 		}
 	}
 
@@ -415,11 +433,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		}
 		if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
 			this.mRegisterNew.setVisibility(View.VISIBLE);
+			this.mChangePassword.setVisibility(View.GONE);
+			this.mChangePassword.setChecked(false);
 			this.mRegisterNew.setChecked(true);
 			this.mPasswordConfirm.setText(this.mAccount.getPassword());
 		} else {
 			this.mRegisterNew.setVisibility(View.GONE);
 			this.mRegisterNew.setChecked(false);
+			this.mChangePassword.setVisibility(View.GONE);
+			this.mChangePassword.setChecked(false);
 		}
 		if (this.mAccount.getStatus() == Account.State.ONLINE
 				&& !this.mFetchingAvatar) {
@@ -474,7 +496,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 					.setOnClickListener(new View.OnClickListener() {
 
 						@Override
-						public void onClick(View v) {
+						public void onClick(final View v) {
 
 							if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) {
 								Toast.makeText(

src/main/java/eu/siacs/conversations/utils/Xmlns.java 🔗

@@ -3,4 +3,5 @@ package eu.siacs.conversations.utils;
 public final class Xmlns {
 	public static final String BLOCKING = "urn:xmpp:blocking";
 	public static final String ROSTER = "jabber:iq:roster";
+	public static final String REGISTER = "jabber:iq:register";
 }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -1082,6 +1082,10 @@ public class XmppConnection implements Runnable {
 			return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING);
 		}
 
+		public boolean register() {
+			return hasDiscoFeature(account.getServer(), Xmlns.REGISTER);
+		}
+
 		public boolean sm() {
 			return streamId != null;
 		}

src/main/res/layout/activity_edit_account.xml 🔗

@@ -79,6 +79,15 @@
                     android:textColor="@color/primarytext"
                     android:textSize="?attr/TextSizeBody" />
 
+                <CheckBox
+                    android:id="@+id/account_change_password"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="8dp"
+                    android:text="@string/change_password"
+                    android:textColor="@color/primarytext"
+                    android:textSize="?attr/TextSizeBody" />
+
                 <TextView
                     android:id="@+id/account_confirm_password_desc"
                     android:layout_width="wrap_content"
@@ -336,8 +345,6 @@
                         android:visibility="visible"
                         android:contentDescription="@string/copy_otr_clipboard_description"/>
                 </RelativeLayout>
-
-               
             </LinearLayout>
         </LinearLayout>
     </ScrollView>

src/main/res/values/strings.xml 🔗

@@ -46,6 +46,7 @@
     <string name="contact_blocked">Contact blocked</string>
     <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string>
     <string name="register_account">Register new account on server</string>
+    <string name="change_password">Change password</string>
     <string name="share_with">Share with</string>
     <string name="start_conversation">Start Conversation</string>
     <string name="invite_contact">Invite Contact</string>
@@ -249,7 +250,7 @@
     <string name="private_message_to">to %s</string>
     <string name="send_private_message_to">Send private message to %s</string>
     <string name="connect">Connect</string>
-    <string name="account_already_exists">This account does already exist</string>
+    <string name="account_already_exists">This account already exists</string>
     <string name="next">Next</string>
     <string name="server_info_session_established">Current session established</string>
     <string name="additional_information">Additional Information</string>