get rid of customizable resources

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Account.java               |  16 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java    |   6 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  11 
src/main/java/eu/siacs/conversations/ui/SettingsActivity.java            |  83 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            | 309 
src/main/res/values/arrays.xml                                           |   7 
src/main/res/values/strings.xml                                          |   2 
src/main/res/xml/preferences.xml                                         |   8 
8 files changed, 191 insertions(+), 251 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Account.java πŸ”—

@@ -42,6 +42,7 @@ public class Account extends AbstractEntity {
 	public static final String PORT = "port";
 	public static final String STATUS = "status";
 	public static final String STATUS_MESSAGE = "status_message";
+	public static final String RESOURCE = "resource";
 
 	public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
 
@@ -229,6 +230,7 @@ public class Account extends AbstractEntity {
 	protected String rosterVersion;
 	protected State status = State.OFFLINE;
 	protected final JSONObject keys;
+	protected String resource;
 	protected String avatar;
 	protected String displayName = null;
 	protected String hostname = null;
@@ -238,7 +240,6 @@ public class Account extends AbstractEntity {
 	private PgpDecryptionService pgpDecryptionService = null;
 	private XmppConnection xmppConnection = null;
 	private long mEndGracePeriod = 0L;
-	private String otrFingerprint;
 	private final Roster roster = new Roster(this);
 	private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
 	private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
@@ -256,9 +257,6 @@ public class Account extends AbstractEntity {
 					final Presence.Status status, String statusMessage) {
 		this.uuid = uuid;
 		this.jid = jid;
-		if (jid.isBareJid()) {
-			this.setResource("mobile");
-		}
 		this.password = password;
 		this.options = options;
 		this.rosterVersion = rosterVersion;
@@ -280,8 +278,10 @@ public class Account extends AbstractEntity {
 	public static Account fromCursor(final Cursor cursor) {
 		Jid jid = null;
 		try {
-			jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
-					cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
+			jid = Jid.fromParts(
+					cursor.getString(cursor.getColumnIndex(USERNAME)),
+					cursor.getString(cursor.getColumnIndex(SERVER)),
+					cursor.getString(cursor.getColumnIndex(RESOURCE)));
 		} catch (final InvalidJidException ignored) {
 		}
 		return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
@@ -317,6 +317,7 @@ public class Account extends AbstractEntity {
 	}
 
 	public boolean setJid(final Jid next) {
+		final Jid previousFull = this.jid;
 		final Jid prev = this.jid != null ? this.jid.toBareJid() : null;
 		final boolean changed = prev == null || (next != null && !prev.equals(next.toBareJid()));
 		if (changed) {
@@ -328,7 +329,7 @@ public class Account extends AbstractEntity {
 			}
 		}
 		this.jid = next;
-		return changed;
+		return next != null && next.equals(previousFull);
 	}
 
 	public Jid getServer() {
@@ -483,6 +484,7 @@ public class Account extends AbstractEntity {
 		values.put(PORT, port);
 		values.put(STATUS, presenceStatus.toShowString());
 		values.put(STATUS_MESSAGE, presenceStatusMessage);
+		values.put(RESOURCE,jid.getResourcepart());
 		return values;
 	}
 

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java πŸ”—

@@ -62,7 +62,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	private static DatabaseBackend instance = null;
 
 	private static final String DATABASE_NAME = "history";
-	private static final int DATABASE_VERSION = 39;
+	private static final int DATABASE_VERSION = 40;
 
 	private static String CREATE_CONTATCS_STATEMENT = "create table "
 			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -184,6 +184,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				+ Account.AVATAR + " TEXT, "
 				+ Account.KEYS + " TEXT, "
 				+ Account.HOSTNAME + " TEXT, "
+				+ Account.RESOURCE + " TEXT,"
 				+ Account.PORT + " NUMBER DEFAULT 5222)");
 		db.execSQL("create table " + Conversation.TABLENAME + " ("
 				+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
@@ -305,6 +306,9 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
 			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
 		}
+		if (oldVersion < 40 && newVersion >= 40) {
+			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.RESOURCE + " TEXT");
+		}
 		/* Any migrations that alter the Account table need to happen BEFORE this migration, as it
 		 * depends on account de-serialization.
 		 */

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java πŸ”—

@@ -1125,17 +1125,6 @@ public class XmppConnectionService extends Service {
 	}
 
 	public XmppConnection createConnection(final Account account) {
-		final SharedPreferences sharedPref = getPreferences();
-		String resource;
-		try {
-			resource = sharedPref.getString("resource", getString(R.string.default_resource)).toLowerCase(Locale.ENGLISH);
-			if (resource.trim().isEmpty()) {
-				throw new Exception();
-			}
-		} catch (Exception e) {
-			resource = "conversations";
-		}
-		account.setResource(resource);
 		final XmppConnection connection = new XmppConnection(account, this);
 		connection.setOnMessagePacketReceivedListener(this.mMessageParser);
 		connection.setOnStatusChangedListener(this.statusListener);

src/main/java/eu/siacs/conversations/ui/SettingsActivity.java πŸ”—

@@ -1,5 +1,6 @@
 package eu.siacs.conversations.ui;
 
+import android.support.annotation.NonNull;
 import android.support.v7.app.AlertDialog;
 import android.app.FragmentManager;
 import android.content.DialogInterface;
@@ -68,7 +69,7 @@ public class SettingsActivity extends XmppActivity implements
 
 		this.mTheme = findTheme();
 		setTheme(this.mTheme);
-		getWindow().getDecorView().setBackgroundColor(Color.get(this,R.attr.color_background_primary));
+		getWindow().getDecorView().setBackgroundColor(Color.get(this, R.attr.color_background_primary));
 
 	}
 
@@ -81,15 +82,6 @@ public class SettingsActivity extends XmppActivity implements
 	public void onStart() {
 		super.onStart();
 		PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
-		ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
-		if (resources != null) {
-			ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
-			if (!entries.contains(Build.MODEL)) {
-				entries.add(0, Build.MODEL);
-				resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
-				resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
-			}
-		}
 
 		if (Config.FORCE_ORBOT) {
 			PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
@@ -281,37 +273,31 @@ public class SettingsActivity extends XmppActivity implements
 			}
 		}
 		final boolean[] checkedItems = new boolean[accounts.size()];
-		builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
-			@Override
-			public void onClick(DialogInterface dialog, int which, boolean isChecked) {
-				checkedItems[which] = isChecked;
-				final AlertDialog alertDialog = (AlertDialog) dialog;
-				for (boolean item : checkedItems) {
-					if (item) {
-						alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
-						return;
-					}
+		builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, (dialog, which, isChecked) -> {
+			checkedItems[which] = isChecked;
+			final AlertDialog alertDialog = (AlertDialog) dialog;
+			for (boolean item : checkedItems) {
+				if (item) {
+					alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+					return;
 				}
-				alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
 			}
+			alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
 		});
 		builder.setNegativeButton(R.string.cancel, null);
-		builder.setPositiveButton(R.string.delete_selected_keys, new DialogInterface.OnClickListener() {
-			@Override
-			public void onClick(DialogInterface dialog, int which) {
-				for (int i = 0; i < checkedItems.length; ++i) {
-					if (checkedItems[i]) {
-						try {
-							Jid jid = Jid.fromString(accounts.get(i).toString());
-							Account account = xmppConnectionService.findAccountByJid(jid);
-							if (account != null) {
-								account.getAxolotlService().regenerateKeys(true);
-							}
-						} catch (InvalidJidException e) {
-							//
+		builder.setPositiveButton(R.string.delete_selected_keys, (dialog, which) -> {
+			for (int i = 0; i < checkedItems.length; ++i) {
+				if (checkedItems[i]) {
+					try {
+						Jid jid = Jid.fromString(accounts.get(i).toString());
+						Account account = xmppConnectionService.findAccountByJid(jid);
+						if (account != null) {
+							account.getAxolotlService().regenerateKeys(true);
 						}
-
+					} catch (InvalidJidException e) {
+						//
 					}
+
 				}
 			}
 		});
@@ -338,23 +324,7 @@ public class SettingsActivity extends XmppActivity implements
 				TREAT_VIBRATE_AS_SILENT,
 				MANUALLY_CHANGE_PRESENCE,
 				BROADCAST_LAST_ACTIVITY);
-		if (name.equals("resource")) {
-			String resource = preferences.getString("resource", "mobile")
-					.toLowerCase(Locale.US);
-			if (xmppConnectionServiceBound) {
-				for (Account account : xmppConnectionService.getAccounts()) {
-					if (account.setResource(resource)) {
-						if (account.isEnabled()) {
-							XmppConnection connection = account.getXmppConnection();
-							if (connection != null) {
-								connection.resetStreamId();
-							}
-							xmppConnectionService.reconnectAccountInBackground(account);
-						}
-					}
-				}
-			}
-		} else if (name.equals(KEEP_FOREGROUND_SERVICE)) {
+		if (name.equals(KEEP_FOREGROUND_SERVICE)) {
 			xmppConnectionService.toggleForegroundService();
 		} else if (resendPresence.contains(name)) {
 			if (xmppConnectionServiceBound) {
@@ -383,7 +353,7 @@ public class SettingsActivity extends XmppActivity implements
 	}
 
 	@Override
-	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 		if (grantResults.length > 0)
 			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 				if (requestCode == REQUEST_WRITE_LOGS) {
@@ -399,12 +369,7 @@ public class SettingsActivity extends XmppActivity implements
 	}
 
 	private void displayToast(final String msg) {
-		runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
-			}
-		});
+		runOnUiThread(() -> Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show());
 	}
 
 	private void reconnectAccounts() {

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java πŸ”—

@@ -50,6 +50,7 @@ import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.DomainHostnameVerifier;
 import eu.siacs.conversations.crypto.XmppDomainVerifier;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -102,21 +103,49 @@ public class XmppConnection implements Runnable {
 	private static final int PACKET_IQ = 0;
 	private static final int PACKET_MESSAGE = 1;
 	private static final int PACKET_PRESENCE = 2;
+	public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() {
+		@Override
+		public void onIqPacketReceived(Account account, IqPacket packet) {
+			if (packet.getType() == IqPacket.TYPE.RESULT) {
+				account.setOption(Account.OPTION_REGISTER, false);
+				throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
+			} else {
+				final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
+						"The password is too weak",
+						"Please use a longer password.");
+				Element error = packet.findChild("error");
+				Account.State state = Account.State.REGISTRATION_FAILED;
+				if (error != null) {
+					if (error.hasChild("conflict")) {
+						state = Account.State.REGISTRATION_CONFLICT;
+					} else if (error.hasChild("resource-constraint")
+							&& "wait".equals(error.getAttribute("type"))) {
+						state = Account.State.REGISTRATION_PLEASE_WAIT;
+					} else if (error.hasChild("not-acceptable")
+							&& PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) {
+						state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
+					}
+				}
+				throw new StateChangingError(state);
+			}
+		}
+	};
 	protected final Account account;
+	private final Features features = new Features(this);
+	private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
+	private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
+	private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>();
+	private final ArrayList<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new ArrayList<>();
+	private final XmppConnectionService mXmppConnectionService;
 	private Socket socket;
 	private XmlReader tagReader;
 	private TagWriter tagWriter = new TagWriter();
-	private final Features features = new Features(this);
 	private boolean shouldAuthenticate = true;
 	private boolean inSmacksSession = false;
 	private boolean isBound = false;
 	private Element streamFeatures;
-	private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
-
 	private String streamId = null;
 	private int smVersion = 3;
-	private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
-
 	private int stanzasReceived = 0;
 	private int stanzasSent = 0;
 	private long lastPacketReceived = 0;
@@ -130,94 +159,19 @@ public class XmppConnection implements Runnable {
 	private AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
 	private boolean mInteractive = false;
 	private int attempt = 0;
-	private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>();
 	private OnPresencePacketReceived presenceListener = null;
 	private OnJinglePacketReceived jingleListener = null;
 	private OnIqPacketReceived unregisteredIqListener = null;
 	private OnMessagePacketReceived messageListener = null;
 	private OnStatusChanged statusListener = null;
 	private OnBindListener bindListener = null;
-	private final ArrayList<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new ArrayList<>();
 	private OnMessageAcknowledged acknowledgedListener = null;
-	private final XmppConnectionService mXmppConnectionService;
-
 	private SaslMechanism saslMechanism;
 	private URL redirectionUrl = null;
 	private String verifiedHostname = null;
 	private Thread mThread;
 	private CountDownLatch mStreamCountDownLatch;
 
-	private class MyKeyManager implements X509KeyManager {
-		@Override
-		public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
-			return account.getPrivateKeyAlias();
-		}
-
-		@Override
-		public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
-			return null;
-		}
-
-		@Override
-		public X509Certificate[] getCertificateChain(String alias) {
-			Log.d(Config.LOGTAG, "getting certificate chain");
-			try {
-				return KeyChain.getCertificateChain(mXmppConnectionService, alias);
-			} catch (Exception e) {
-				Log.d(Config.LOGTAG, e.getMessage());
-				return new X509Certificate[0];
-			}
-		}
-
-		@Override
-		public String[] getClientAliases(String s, Principal[] principals) {
-			final String alias = account.getPrivateKeyAlias();
-			return alias != null ? new String[]{alias} : new String[0];
-		}
-
-		@Override
-		public String[] getServerAliases(String s, Principal[] principals) {
-			return new String[0];
-		}
-
-		@Override
-		public PrivateKey getPrivateKey(String alias) {
-			try {
-				return KeyChain.getPrivateKey(mXmppConnectionService, alias);
-			} catch (Exception e) {
-				return null;
-			}
-		}
-	}
-
-	public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() {
-		@Override
-		public void onIqPacketReceived(Account account, IqPacket packet) {
-			if (packet.getType() == IqPacket.TYPE.RESULT) {
-				account.setOption(Account.OPTION_REGISTER, false);
-				throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
-			} else {
-				final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
-						"The password is too weak",
-						"Please use a longer password.");
-				Element error = packet.findChild("error");
-				Account.State state = Account.State.REGISTRATION_FAILED;
-				if (error != null) {
-					if (error.hasChild("conflict")) {
-						state = Account.State.REGISTRATION_CONFLICT;
-					} else if (error.hasChild("resource-constraint")
-							&& "wait".equals(error.getAttribute("type"))) {
-						state = Account.State.REGISTRATION_PLEASE_WAIT;
-					} else if (error.hasChild("not-acceptable")
-							&& PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) {
-						state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
-					}
-				}
-				throw new StateChangingError(state);
-			}
-		}
-	};
-
 	public XmppConnection(final Account account, final XmppConnectionService service) {
 		this.account = account;
 		final String tag = account.getJid().toBareJid().toPreppedString();
@@ -479,19 +433,6 @@ public class XmppConnection implements Runnable {
 		return tag != null && tag.isStart("stream");
 	}
 
-	private static class TlsFactoryVerifier {
-		private final SSLSocketFactory factory;
-		private final DomainHostnameVerifier verifier;
-
-		public TlsFactoryVerifier(final SSLSocketFactory factory, final DomainHostnameVerifier verifier) throws IOException {
-			this.factory = factory;
-			this.verifier = verifier;
-			if (factory == null || verifier == null) {
-				throw new IOException("could not setup ssl");
-			}
-		}
-	}
-
 	private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException {
 		final SSLContext sc = SSLSocketHelper.getSSLContext();
 		MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager();
@@ -836,7 +777,6 @@ public class XmppConnection implements Runnable {
 		tagWriter.writeTag(startTLS);
 	}
 
-
 	private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
 		tagReader.readTag();
 		try {
@@ -1069,55 +1009,52 @@ public class XmppConnection implements Runnable {
 			return;
 		}
 		clearIqCallbacks();
+		if (account.getJid().isBareJid()) {
+			account.setResource(this.createNewResource());
+		}
 		final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
 		final String resource = Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource();
 		iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
-		this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() {
-			@Override
-			public void onIqPacketReceived(final Account account, final IqPacket packet) {
-				if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
-					return;
-				}
-				final Element bind = packet.findChild("bind");
-				if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
-					isBound = true;
-					final Element jid = bind.findChild("jid");
-					if (jid != null && jid.getContent() != null) {
-						try {
-							Jid assignedJid = Jid.fromString(jid.getContent());
-							if (!account.getJid().getDomainpart().equals(assignedJid.getDomainpart())) {
-								Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server tried to re-assign domain to "+assignedJid.getDomainpart());
-								throw new StateChangingError(Account.State.BIND_FAILURE);
-							}
-							if (account.setJid(assignedJid)) {
-								Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": bare jid changed during bind. updating database");
-								mXmppConnectionService.databaseBackend.updateAccount(account);
-							}
-							if (streamFeatures.hasChild("session")
-									&& !streamFeatures.findChild("session").hasChild("optional")) {
-								sendStartSession();
-							} else {
-								sendPostBindInitialization();
-							}
-							return;
-						} catch (final InvalidJidException e) {
-							Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server reported invalid jid (" + jid.getContent() + ") on bind");
+		this.sendUnmodifiedIqPacket(iq, (account, packet) -> {
+			if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+				return;
+			}
+			final Element bind = packet.findChild("bind");
+			if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
+				isBound = true;
+				final Element jid = bind.findChild("jid");
+				if (jid != null && jid.getContent() != null) {
+					try {
+						Jid assignedJid = Jid.fromString(jid.getContent());
+						if (!account.getJid().getDomainpart().equals(assignedJid.getDomainpart())) {
+							Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server tried to re-assign domain to "+assignedJid.getDomainpart());
+							throw new StateChangingError(Account.State.BIND_FAILURE);
 						}
-					} else {
-						Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
+						if (account.setJid(assignedJid)) {
+							Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": jid changed during bind. updating database");
+							mXmppConnectionService.databaseBackend.updateAccount(account);
+						}
+						if (streamFeatures.hasChild("session")
+								&& !streamFeatures.findChild("session").hasChild("optional")) {
+							sendStartSession();
+						} else {
+							sendPostBindInitialization();
+						}
+						return;
+					} catch (final InvalidJidException e) {
+						Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server reported invalid jid (" + jid.getContent() + ") on bind");
 					}
 				} else {
-					Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString());
+					Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
 				}
-				final Element error = packet.findChild("error");
-				final String resource = account.getResource().split("\\.")[0];
-				if (packet.getType() == IqPacket.TYPE.ERROR && error != null && error.hasChild("conflict")) {
-					account.setResource(resource + "." + nextRandomId());
-				} else {
-					account.setResource(resource);
-				}
-				throw new StateChangingError(Account.State.BIND_FAILURE);
+			} else {
+				Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString());
 			}
+			final Element error = packet.findChild("error");
+			if (packet.getType() == IqPacket.TYPE.ERROR && error != null && error.hasChild("conflict")) {
+				account.setResource(createNewResource());
+			}
+			throw new StateChangingError(Account.State.BIND_FAILURE);
 		},true);
 	}
 
@@ -1337,18 +1274,14 @@ public class XmppConnection implements Runnable {
 		});
 	}
 
-	private void processStreamError(final Tag currentTag)
-			throws XmlPullParserException, IOException {
+	private void processStreamError(final Tag currentTag) throws XmlPullParserException, IOException {
 		final Element streamError = tagReader.readElement(currentTag);
 		if (streamError == null) {
 			return;
 		}
 		if (streamError.hasChild("conflict")) {
-			final String resource = account.getResource().split("\\.")[0];
-			account.setResource(resource + "." + nextRandomId());
-			Log.d(Config.LOGTAG,
-					account.getJid().toBareJid() + ": switching resource due to conflict ("
-							+ account.getResource() + ")");
+			account.setResource(createNewResource());
+			Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": switching resource due to conflict (" + account.getResource() + ")");
 			throw new IOException();
 		} else if (streamError.hasChild("host-unknown")) {
 			throw new StateChangingException(Account.State.HOST_UNKNOWN);
@@ -1370,8 +1303,16 @@ public class XmppConnection implements Runnable {
 		tagWriter.writeTag(stream);
 	}
 
+	private String createNewResource() {
+		return mXmppConnectionService.getString(R.string.app_name)+'.'+nextRandomId(true);
+	}
+
 	private String nextRandomId() {
-		return CryptoHelper.random(10, mXmppConnectionService.getRNG());
+		return nextRandomId(false);
+	}
+
+	private String nextRandomId(boolean s) {
+		return CryptoHelper.random(s ? 3 : 9, mXmppConnectionService.getRNG());
 	}
 
 	public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
@@ -1662,6 +1603,75 @@ public class XmppConnection implements Runnable {
 		return Identity.UNKNOWN;
 	}
 
+	private IqGenerator getIqGenerator() {
+		return mXmppConnectionService.getIqGenerator();
+	}
+
+	public enum Identity {
+		FACEBOOK,
+		SLACK,
+		EJABBERD,
+		PROSODY,
+		NIMBUZZ,
+		UNKNOWN
+	}
+
+	private static class TlsFactoryVerifier {
+		private final SSLSocketFactory factory;
+		private final DomainHostnameVerifier verifier;
+
+		public TlsFactoryVerifier(final SSLSocketFactory factory, final DomainHostnameVerifier verifier) throws IOException {
+			this.factory = factory;
+			this.verifier = verifier;
+			if (factory == null || verifier == null) {
+				throw new IOException("could not setup ssl");
+			}
+		}
+	}
+
+	private class MyKeyManager implements X509KeyManager {
+		@Override
+		public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
+			return account.getPrivateKeyAlias();
+		}
+
+		@Override
+		public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
+			return null;
+		}
+
+		@Override
+		public X509Certificate[] getCertificateChain(String alias) {
+			Log.d(Config.LOGTAG, "getting certificate chain");
+			try {
+				return KeyChain.getCertificateChain(mXmppConnectionService, alias);
+			} catch (Exception e) {
+				Log.d(Config.LOGTAG, e.getMessage());
+				return new X509Certificate[0];
+			}
+		}
+
+		@Override
+		public String[] getClientAliases(String s, Principal[] principals) {
+			final String alias = account.getPrivateKeyAlias();
+			return alias != null ? new String[]{alias} : new String[0];
+		}
+
+		@Override
+		public String[] getServerAliases(String s, Principal[] principals) {
+			return new String[0];
+		}
+
+		@Override
+		public PrivateKey getPrivateKey(String alias) {
+			try {
+				return KeyChain.getPrivateKey(mXmppConnectionService, alias);
+			} catch (Exception e) {
+				return null;
+			}
+		}
+	}
+
 	private class StateChangingError extends Error {
 		private final Account.State state;
 
@@ -1678,15 +1688,6 @@ public class XmppConnection implements Runnable {
 		}
 	}
 
-	public enum Identity {
-		FACEBOOK,
-		SLACK,
-		EJABBERD,
-		PROSODY,
-		NIMBUZZ,
-		UNKNOWN
-	}
-
 	public class Features {
 		XmppConnection connection;
 		private boolean carbonsEnabled = false;
@@ -1818,8 +1819,4 @@ public class XmppConnection implements Runnable {
 			return hasDiscoFeature(account.getJid().toBareJid(), Namespace.STANZA_IDS);
 		}
 	}
-
-	private IqGenerator getIqGenerator() {
-		return mXmppConnectionService.getIqGenerator();
-	}
 }

src/main/res/values/arrays.xml πŸ”—

@@ -1,13 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
-	<string-array name="resources">
-		<item>Mobile</item>
-		<item>Phone</item>
-		<item>Tablet</item>
-		<item>@string/app_name</item>
-		<item>Android</item>
-	</string-array>
 	<string-array name="themes">
 		<item>@string/pref_theme_light</item>
 		<item>@string/pref_theme_dark</item>

src/main/res/values/strings.xml πŸ”—

@@ -100,8 +100,6 @@
 	<string name="no_pgp_keys">No OpenPGP Keys found</string>
 	<string name="contacts_have_no_pgp_keys">Conversations is unable to encrypt your messages because your contacts are not announcing their public key.\n\n<small>Please ask your contacts to setup OpenPGP.</small></string>
 	<string name="pref_general">General</string>
-	<string name="pref_xmpp_resource">XMPP resource</string>
-	<string name="pref_xmpp_resource_summary">The name this client identifies itself with</string>
 	<string name="pref_accept_files">Accept files</string>
 	<string name="pref_accept_files_summary">Automatically accept files smaller than…</string>
 	<string name="pref_attachments">Attachments</string>

src/main/res/xml/preferences.xml πŸ”—

@@ -9,14 +9,6 @@
             android:key="grant_new_contacts"
             android:summary="@string/pref_grant_presence_updates_summary"
             android:title="@string/pref_grant_presence_updates"/>
-
-        <ListPreference
-            android:defaultValue="@string/default_resource"
-            android:entries="@array/resources"
-            android:entryValues="@array/resources"
-            android:key="resource"
-            android:summary="@string/pref_xmpp_resource_summary"
-            android:title="@string/pref_xmpp_resource"/>
         <PreferenceScreen
             android:key="huawei"
             android:title="@string/huawei_protected_apps"