initial tor support

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/Config.java                            |   4 
src/main/java/eu/siacs/conversations/entities/Account.java                  |  50 
src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java        |  14 
src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java       |  21 
src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java         |  11 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java       | 222 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java    |  11 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java            |  43 
src/main/java/eu/siacs/conversations/ui/SettingsActivity.java               |   2 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java               |  18 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java |   6 
src/main/res/layout/activity_edit_account.xml                               |  54 
src/main/res/menu/editaccount.xml                                           |   5 
src/main/res/values/strings.xml                                             |   8 
src/main/res/xml/preferences.xml                                            |   7 
15 files changed, 346 insertions(+), 130 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/Config.java 🔗

@@ -2,6 +2,10 @@ package eu.siacs.conversations;
 
 import android.graphics.Bitmap;
 
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 
 public final class Config {

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

@@ -2,6 +2,8 @@ package eu.siacs.conversations.entities;
 
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Parcelable;
 import android.os.SystemClock;
 
 import eu.siacs.conversations.crypto.PgpDecryptionService;
@@ -13,6 +15,7 @@ import org.json.JSONObject;
 
 import java.security.PublicKey;
 import java.security.interfaces.DSAPublicKey;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,6 +42,8 @@ public class Account extends AbstractEntity {
 	public static final String KEYS = "keys";
 	public static final String AVATAR = "avatar";
 	public static final String DISPLAY_NAME = "display_name";
+	public static final String HOSTNAME = "hostname";
+	public static final String PORT = "port";
 
 	public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
 
@@ -67,7 +72,20 @@ public class Account extends AbstractEntity {
 		}
 	}
 
-	public static enum State {
+	public ArrayList<Parcelable> getHostnamePortBundles() {
+		ArrayList<Parcelable> values = new ArrayList<>();
+		Bundle hostPort = new Bundle();
+		if (hostname != null && !hostname.isEmpty()) {
+			hostPort.putString("name", hostname);
+		} else {
+			hostPort.putString("name", getServer().toString());
+		}
+		hostPort.putInt("port", port);
+		values.add(hostPort);
+		return values;
+	}
+
+	public enum State {
 		DISABLED,
 		OFFLINE,
 		CONNECTING,
@@ -147,6 +165,8 @@ public class Account extends AbstractEntity {
 	protected JSONObject keys = new JSONObject();
 	protected String avatar;
 	protected String displayName = null;
+	protected String hostname = null;
+	protected int port = 5222;
 	protected boolean online = false;
 	private OtrService mOtrService = null;
 	private AxolotlService axolotlService = null;
@@ -164,12 +184,12 @@ public class Account extends AbstractEntity {
 
 	public Account(final Jid jid, final String password) {
 		this(java.util.UUID.randomUUID().toString(), jid,
-				password, 0, null, "", null, null);
+				password, 0, null, "", null, null, null, 5222);
 	}
 
 	public Account(final String uuid, final Jid jid,
 			final String password, final int options, final String rosterVersion, final String keys,
-			final String avatar, String displayName) {
+			final String avatar, String displayName, String hostname, int port) {
 		this.uuid = uuid;
 		this.jid = jid;
 		if (jid.isBareJid()) {
@@ -185,6 +205,8 @@ public class Account extends AbstractEntity {
 		}
 		this.avatar = avatar;
 		this.displayName = displayName;
+		this.hostname = hostname;
+		this.port = port;
 	}
 
 	public static Account fromCursor(final Cursor cursor) {
@@ -201,7 +223,9 @@ public class Account extends AbstractEntity {
 				cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
 				cursor.getString(cursor.getColumnIndex(KEYS)),
 				cursor.getString(cursor.getColumnIndex(AVATAR)),
-				cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
+				cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)),
+				cursor.getString(cursor.getColumnIndex(HOSTNAME)),
+				cursor.getInt(cursor.getColumnIndex(PORT)));
 	}
 
 	public boolean isOptionSet(final int option) {
@@ -236,6 +260,22 @@ public class Account extends AbstractEntity {
 		this.password = password;
 	}
 
+	public void setHostname(String hostname) {
+		this.hostname = hostname;
+	}
+
+	public String getHostname() {
+		return this.hostname == null ? "" : this.hostname;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	public int getPort() {
+		return this.port;
+	}
+
 	public State getStatus() {
 		if (isOptionSet(OPTION_DISABLED)) {
 			return State.DISABLED;
@@ -314,6 +354,8 @@ public class Account extends AbstractEntity {
 		values.put(ROSTERVERSION, rosterVersion);
 		values.put(AVATAR, avatar);
 		values.put(DISPLAY_NAME, displayName);
+		values.put(HOSTNAME, hostname);
+		values.put(PORT, port);
 		return values;
 	}
 

src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java 🔗

@@ -1,7 +1,13 @@
 package eu.siacs.conversations.http;
 
+import android.os.Build;
+
 import org.apache.http.conn.ssl.StrictHostnameVerifier;
 
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
@@ -87,4 +93,12 @@ public class HttpConnectionManager extends AbstractConnectionManager {
 		} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
 		}
 	}
+
+	public Proxy getProxy() throws IOException {
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+			return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
+		} else {
+			return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118));
+		}
+	}
 }

src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java 🔗

@@ -10,7 +10,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.Proxy;
 import java.net.URL;
 import java.util.Arrays;
 
@@ -39,10 +42,12 @@ public class HttpDownloadConnection implements Transferable {
 	private int mStatus = Transferable.STATUS_UNKNOWN;
 	private boolean acceptedAutomatically = false;
 	private int mProgress = 0;
+	private boolean mUseTor = false;
 
 	public HttpDownloadConnection(HttpConnectionManager manager) {
 		this.mHttpConnectionManager = manager;
 		this.mXmppConnectionService = manager.getXmppConnectionService();
+		this.mUseTor = mXmppConnectionService.useTorToConnect();
 	}
 
 	@Override
@@ -191,8 +196,15 @@ public class HttpDownloadConnection implements Transferable {
 			try {
 				Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
 				changeStatus(STATUS_CHECKING);
-				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+				HttpURLConnection connection;
+				if (mUseTor) {
+					connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
+				} else {
+					connection = (HttpURLConnection) mUrl.openConnection();
+				}
 				connection.setRequestMethod("HEAD");
+				Log.d(Config.LOGTAG,"url: "+connection.getURL().toString());
+				Log.d(Config.LOGTAG,"connection: "+connection.toString());
 				connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
 				if (connection instanceof HttpsURLConnection) {
 					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
@@ -245,7 +257,12 @@ public class HttpDownloadConnection implements Transferable {
 			PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
 			try {
 				wakeLock.acquire();
-				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+				HttpURLConnection connection;
+				if (mUseTor) {
+					connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
+				} else {
+					connection = (HttpURLConnection) mUrl.openConnection();
+				}
 				if (connection instanceof HttpsURLConnection) {
 					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
 				}

src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java 🔗

@@ -12,7 +12,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.Proxy;
 import java.net.URL;
 
 import javax.net.ssl.HttpsURLConnection;
@@ -46,6 +49,7 @@ public class HttpUploadConnection implements Transferable {
 	private String mime;
 	private URL mGetUrl;
 	private URL mPutUrl;
+	private boolean mUseTor = false;
 
 	private byte[] key = null;
 
@@ -56,6 +60,7 @@ public class HttpUploadConnection implements Transferable {
 	public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
 		this.mHttpConnectionManager = httpConnectionManager;
 		this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
+		this.mUseTor = mXmppConnectionService.useTorToConnect();
 	}
 
 	@Override
@@ -158,7 +163,11 @@ public class HttpUploadConnection implements Transferable {
 			try {
 				wakeLock.acquire();
 				Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
-				connection = (HttpURLConnection) mPutUrl.openConnection();
+				if (mUseTor) {
+					connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy());
+				} else {
+					connection = (HttpURLConnection) mPutUrl.openConnection();
+				}
 				if (connection instanceof HttpsURLConnection) {
 					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
 				}

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java 🔗

@@ -43,7 +43,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	private static DatabaseBackend instance = null;
 
 	private static final String DATABASE_NAME = "history";
-	private static final int DATABASE_VERSION = 19;
+	private static final int DATABASE_VERSION = 20;
 
 	private static String CREATE_CONTATCS_STATEMENT = "create table "
 			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -59,41 +59,41 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
 			+ SQLiteAxolotlStore.PREKEY_TABLENAME + "("
-				+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-				+ SQLiteAxolotlStore.ID + " INTEGER, "
-				+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-					+ SQLiteAxolotlStore.ACCOUNT
-				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-				+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-					+ SQLiteAxolotlStore.ID
-				+ ") ON CONFLICT REPLACE"
-			+");";
+			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
+			+ SQLiteAxolotlStore.ID + " INTEGER, "
+			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+			+ SQLiteAxolotlStore.ACCOUNT
+			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+			+ SQLiteAxolotlStore.ID
+			+ ") ON CONFLICT REPLACE"
+			+ ");";
 
 	private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
 			+ SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
-				+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-				+ SQLiteAxolotlStore.ID + " INTEGER, "
-				+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-					+ SQLiteAxolotlStore.ACCOUNT
-				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-				+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-					+ SQLiteAxolotlStore.ID
-				+ ") ON CONFLICT REPLACE"+
+			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
+			+ SQLiteAxolotlStore.ID + " INTEGER, "
+			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+			+ SQLiteAxolotlStore.ACCOUNT
+			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+			+ SQLiteAxolotlStore.ID
+			+ ") ON CONFLICT REPLACE" +
 			");";
 
 	private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
 			+ SQLiteAxolotlStore.SESSION_TABLENAME + "("
-				+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-				+ SQLiteAxolotlStore.NAME + " TEXT, "
-				+ SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
-				+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-					+ SQLiteAxolotlStore.ACCOUNT
-				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-				+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-					+ SQLiteAxolotlStore.NAME + ", "
-					+ SQLiteAxolotlStore.DEVICE_ID
-				+ ") ON CONFLICT REPLACE"
-			+");";
+			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
+			+ SQLiteAxolotlStore.NAME + " TEXT, "
+			+ SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
+			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+			+ SQLiteAxolotlStore.ACCOUNT
+			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+			+ SQLiteAxolotlStore.NAME + ", "
+			+ SQLiteAxolotlStore.DEVICE_ID
+			+ ") ON CONFLICT REPLACE"
+			+ ");";
 
 	private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
 			+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
@@ -106,10 +106,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			+ SQLiteAxolotlStore.ACCOUNT
 			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-				+ SQLiteAxolotlStore.NAME + ", "
-				+ SQLiteAxolotlStore.FINGERPRINT
+			+ SQLiteAxolotlStore.NAME + ", "
+			+ SQLiteAxolotlStore.FINGERPRINT
 			+ ") ON CONFLICT IGNORE"
-			+");";
+			+ ");";
 
 	private DatabaseBackend(Context context) {
 		super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -124,7 +124,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				+ Account.DISPLAY_NAME + " TEXT, "
 				+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
 				+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
-				+ " TEXT)");
+				+ " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)");
 		db.execSQL("create table " + Conversation.TABLENAME + " ("
 				+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
 				+ " TEXT, " + Conversation.CONTACT + " TEXT, "
@@ -202,23 +202,23 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		if (oldVersion < 11 && newVersion >= 11) {
 			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 					+ Contact.GROUPS + " TEXT");
-			db.execSQL("delete from "+Contact.TABLENAME);
-			db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL");
+			db.execSQL("delete from " + Contact.TABLENAME);
+			db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
 		}
 		if (oldVersion < 12 && newVersion >= 12) {
 			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 					+ Message.SERVER_MSG_ID + " TEXT");
 		}
 		if (oldVersion < 13 && newVersion >= 13) {
-			db.execSQL("delete from "+Contact.TABLENAME);
-			db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL");
+			db.execSQL("delete from " + Contact.TABLENAME);
+			db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
 		}
 		if (oldVersion < 14 && newVersion >= 14) {
 			// migrate db to new, canonicalized JID domainpart representation
 
 			// Conversation table
 			Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
-			while(cursor.moveToNext()) {
+			while (cursor.moveToNext()) {
 				String newJid;
 				try {
 					newJid = Jid.fromString(
@@ -226,8 +226,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 					).toString();
 				} catch (InvalidJidException ignored) {
 					Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
-							+cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
-							+": " + ignored +". Skipping...");
+							+ cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+							+ ": " + ignored + ". Skipping...");
 					continue;
 				}
 
@@ -236,14 +236,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 						cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
 				};
 				db.execSQL("update " + Conversation.TABLENAME
-						+ " set " + Conversation.CONTACTJID	+ " = ? "
+						+ " set " + Conversation.CONTACTJID + " = ? "
 						+ " where " + Conversation.UUID + " = ?", updateArgs);
 			}
 			cursor.close();
 
 			// Contact table
 			cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
-			while(cursor.moveToNext()) {
+			while (cursor.moveToNext()) {
 				String newJid;
 				try {
 					newJid = Jid.fromString(
@@ -251,8 +251,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 					).toString();
 				} catch (InvalidJidException ignored) {
 					Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
-							+cursor.getString(cursor.getColumnIndex(Contact.JID))
-							+": " + ignored +". Skipping...");
+							+ cursor.getString(cursor.getColumnIndex(Contact.JID))
+							+ ": " + ignored + ". Skipping...");
 					continue;
 				}
 
@@ -270,7 +270,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 			// Account table
 			cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
-			while(cursor.moveToNext()) {
+			while (cursor.moveToNext()) {
 				String newServer;
 				try {
 					newServer = Jid.fromParts(
@@ -280,8 +280,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 					).getDomainpart();
 				} catch (InvalidJidException ignored) {
 					Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
-							+cursor.getString(cursor.getColumnIndex(Account.SERVER))
-							+": " + ignored +". Skipping...");
+							+ cursor.getString(cursor.getColumnIndex(Account.SERVER))
+							+ ": " + ignored + ". Skipping...");
 					continue;
 				}
 
@@ -295,7 +295,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			}
 			cursor.close();
 		}
-		if (oldVersion < 15  && newVersion >= 15) {
+		if (oldVersion < 15 && newVersion >= 15) {
 			recreateAxolotlDb(db);
 			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 					+ Message.FINGERPRINT + " TEXT");
@@ -305,7 +305,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 					+ Message.CARBON + " INTEGER");
 		}
 		if (oldVersion < 19 && newVersion >= 19) {
-			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "+ Account.DISPLAY_NAME+ " TEXT");
+			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
+		}
+		if (oldVersion < 20 && newVersion >= 20) {
+			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
+			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
 		}
 		/* Any migrations that alter the Account table need to happen BEFORE this migration, as it
 		 * depends on account de-serialization.
@@ -314,7 +318,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			List<Account> accounts = getAccounts(db);
 			for (Account account : accounts) {
 				String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
-				if ( ownDeviceIdString == null ) {
+				if (ownDeviceIdString == null) {
 					continue;
 				}
 				int ownDeviceId = Integer.valueOf(ownDeviceIdString);
@@ -324,12 +328,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				if (identityKeyPair != null) {
 					setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED);
 				} else {
-					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not load own identity key pair");
+					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
 				}
 			}
 		}
 		if (oldVersion < 18 && newVersion >= 18) {
-			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "+ Message.READ+ " NUMBER DEFAULT 1");
+			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
 		}
 	}
 
@@ -374,7 +378,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public CopyOnWriteArrayList<Conversation> getConversations(int status) {
 		CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = { Integer.toString(status) };
+		String[] selectionArgs = {Integer.toString(status)};
 		Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
 				+ " where " + Conversation.STATUS + " = ? order by "
 				+ Conversation.CREATED + " desc", selectionArgs);
@@ -390,20 +394,20 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	}
 
 	public ArrayList<Message> getMessages(Conversation conversation, int limit,
-			long timestamp) {
+										  long timestamp) {
 		ArrayList<Message> list = new ArrayList<>();
 		SQLiteDatabase db = this.getReadableDatabase();
 		Cursor cursor;
 		if (timestamp == -1) {
-			String[] selectionArgs = { conversation.getUuid() };
+			String[] selectionArgs = {conversation.getUuid()};
 			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
 					+ "=?", selectionArgs, null, null, Message.TIME_SENT
 					+ " DESC", String.valueOf(limit));
 		} else {
-			String[] selectionArgs = { conversation.getUuid(),
-					Long.toString(timestamp) };
+			String[] selectionArgs = {conversation.getUuid(),
+					Long.toString(timestamp)};
 			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
-					+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
+							+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
 					null, null, Message.TIME_SENT + " DESC",
 					String.valueOf(limit));
 		}
@@ -419,13 +423,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		return list;
 	}
 
-	public Iterable<Message> getMessagesIterable(final Conversation conversation){
+	public Iterable<Message> getMessagesIterable(final Conversation conversation) {
 		return new Iterable<Message>() {
 			@Override
 			public Iterator<Message> iterator() {
-				class MessageIterator implements Iterator<Message>{
+				class MessageIterator implements Iterator<Message> {
 					SQLiteDatabase db = getReadableDatabase();
-					String[] selectionArgs = { conversation.getUuid() };
+					String[] selectionArgs = {conversation.getUuid()};
 					Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
 							+ "=?", selectionArgs, null, null, Message.TIME_SENT
 							+ " ASC", null);
@@ -458,10 +462,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public Conversation findConversation(final Account account, final Jid contactJid) {
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = { account.getUuid(),
+		String[] selectionArgs = {account.getUuid(),
 				contactJid.toBareJid().toString() + "/%",
 				contactJid.toBareJid().toString()
-				};
+		};
 		Cursor cursor = db.query(Conversation.TABLENAME, null,
 				Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
 						+ " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
@@ -475,7 +479,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public void updateConversation(final Conversation conversation) {
 		final SQLiteDatabase db = this.getWritableDatabase();
-		final String[] args = { conversation.getUuid() };
+		final String[] args = {conversation.getUuid()};
 		db.update(Conversation.TABLENAME, conversation.getContentValues(),
 				Conversation.UUID + "=?", args);
 	}
@@ -498,14 +502,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public void updateAccount(Account account) {
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] args = { account.getUuid() };
+		String[] args = {account.getUuid()};
 		db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
 				+ "=?", args);
 	}
 
 	public void deleteAccount(Account account) {
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] args = { account.getUuid() };
+		String[] args = {account.getUuid()};
 		db.delete(Account.TABLENAME, Account.UUID + "=?", args);
 	}
 
@@ -534,7 +538,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public void updateMessage(Message message) {
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] args = { message.getUuid() };
+		String[] args = {message.getUuid()};
 		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
 				+ "=?", args);
 	}
@@ -542,7 +546,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public void readRoster(Roster roster) {
 		SQLiteDatabase db = this.getReadableDatabase();
 		Cursor cursor;
-		String args[] = { roster.getAccount().getUuid() };
+		String args[] = {roster.getAccount().getUuid()};
 		cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
 		while (cursor.moveToNext()) {
 			roster.initContact(Contact.fromCursor(cursor));
@@ -558,7 +562,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				db.insert(Contact.TABLENAME, null, contact.getContentValues());
 			} else {
 				String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
-				String[] whereArgs = { account.getUuid(), contact.getJid().toString() };
+				String[] whereArgs = {account.getUuid(), contact.getJid().toString()};
 				db.delete(Contact.TABLENAME, where, whereArgs);
 			}
 		}
@@ -568,19 +572,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public void deleteMessage(Message message) {
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] args = { message.getUuid() };
+		String[] args = {message.getUuid()};
 		db.delete(Message.TABLENAME, Message.UUID + "=?", args);
 	}
 
 	public void deleteMessagesInConversation(Conversation conversation) {
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] args = { conversation.getUuid() };
+		String[] args = {conversation.getUuid()};
 		db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
 	}
 
 	public Conversation findConversationByUuid(String conversationUuid) {
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = { conversationUuid };
+		String[] selectionArgs = {conversationUuid};
 		Cursor cursor = db.query(Conversation.TABLENAME, null,
 				Conversation.UUID + "=?", selectionArgs, null, null, null);
 		if (cursor.getCount() == 0) {
@@ -594,7 +598,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public Message findMessageByUuid(String messageUuid) {
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = { messageUuid };
+		String[] selectionArgs = {messageUuid};
 		Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
 				selectionArgs, null, null, null);
 		if (cursor.getCount() == 0) {
@@ -608,7 +612,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	public Account findAccountByUuid(String accountUuid) {
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = { accountUuid };
+		String[] selectionArgs = {accountUuid};
 		Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
 				selectionArgs, null, null, null);
 		if (cursor.getCount() == 0) {
@@ -624,9 +628,9 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		ArrayList<Message> list = new ArrayList<>();
 		SQLiteDatabase db = this.getReadableDatabase();
 		Cursor cursor;
-			String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) };
-			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
-					+ "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null);
+		String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE)};
+		cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+				+ "=? AND " + Message.TYPE + "=?", selectionArgs, null, null, null);
 		if (cursor.getCount() > 0) {
 			cursor.moveToLast();
 			do {
@@ -659,10 +663,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public SessionRecord loadSession(Account account, AxolotlAddress contact) {
 		SessionRecord session = null;
 		Cursor cursor = getCursorForSession(account, contact);
-		if(cursor.getCount() != 0) {
+		if (cursor.getCount() != 0) {
 			cursor.moveToFirst();
 			try {
-				session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
+				session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
 			} catch (IOException e) {
 				cursor.close();
 				throw new AssertionError(e);
@@ -689,7 +693,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				selectionArgs,
 				null, null, null);
 
-		while(cursor.moveToNext()) {
+		while (cursor.moveToNext()) {
 			devices.add(cursor.getInt(
 					cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
 		}
@@ -710,7 +714,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		ContentValues values = new ContentValues();
 		values.put(SQLiteAxolotlStore.NAME, contact.getName());
 		values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
-		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
+		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
 		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 		db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
 	}
@@ -757,11 +761,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public PreKeyRecord loadPreKey(Account account, int preKeyId) {
 		PreKeyRecord record = null;
 		Cursor cursor = getCursorForPreKey(account, preKeyId);
-		if(cursor.getCount() != 0) {
+		if (cursor.getCount() != 0) {
 			cursor.moveToFirst();
 			try {
-				record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
-			} catch (IOException e ) {
+				record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+			} catch (IOException e) {
 				throw new AssertionError(e);
 			}
 		}
@@ -780,7 +784,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		SQLiteDatabase db = this.getWritableDatabase();
 		ContentValues values = new ContentValues();
 		values.put(SQLiteAxolotlStore.ID, record.getId());
-		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
+		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
 		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 		db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
 	}
@@ -810,11 +814,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
 		SignedPreKeyRecord record = null;
 		Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
-		if(cursor.getCount() != 0) {
+		if (cursor.getCount() != 0) {
 			cursor.moveToFirst();
 			try {
-				record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
-			} catch (IOException e ) {
+				record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+			} catch (IOException e) {
 				throw new AssertionError(e);
 			}
 		}
@@ -833,7 +837,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				selectionArgs,
 				null, null, null);
 
-		while(cursor.moveToNext()) {
+		while (cursor.moveToNext()) {
 			try {
 				prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
 			} catch (IOException ignored) {
@@ -854,7 +858,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		SQLiteDatabase db = this.getWritableDatabase();
 		ContentValues values = new ContentValues();
 		values.put(SQLiteAxolotlStore.ID, record.getId());
-		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
+		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
 		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 		db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
 	}
@@ -874,7 +878,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	}
 
 	private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
-		return 	getIdentityKeyCursor(db, account, name, own, null);
+		return getIdentityKeyCursor(db, account, name, own, null);
 	}
 
 	private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
@@ -892,16 +896,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		ArrayList<String> selectionArgs = new ArrayList<>(4);
 		selectionArgs.add(account.getUuid());
 		String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
-		if (name != null){
+		if (name != null) {
 			selectionArgs.add(name);
 			selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
 		}
-		if (fingerprint != null){
+		if (fingerprint != null) {
 			selectionArgs.add(fingerprint);
 			selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
 		}
-		if (own != null){
-			selectionArgs.add(own?"1":"0");
+		if (own != null) {
+			selectionArgs.add(own ? "1" : "0");
 			selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
 		}
 		Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
@@ -922,12 +926,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		String name = account.getJid().toBareJid().toString();
 		IdentityKeyPair identityKeyPair = null;
 		Cursor cursor = getIdentityKeyCursor(db, account, name, true);
-		if(cursor.getCount() != 0) {
+		if (cursor.getCount() != 0) {
 			cursor.moveToFirst();
 			try {
-				identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
+				identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
 			} catch (InvalidKeyException e) {
-				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
+				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
 			}
 		}
 		cursor.close();
@@ -943,16 +947,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		Set<IdentityKey> identityKeys = new HashSet<>();
 		Cursor cursor = getIdentityKeyCursor(account, name, false);
 
-		while(cursor.moveToNext()) {
-			if ( trust != null &&
+		while (cursor.moveToNext()) {
+			if (trust != null &&
 					cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
 							!= trust.getCode()) {
 				continue;
 			}
 			try {
-				identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
+				identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
 			} catch (InvalidKeyException e) {
-				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
+				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
 			}
 		}
 		cursor.close();
@@ -970,8 +974,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		};
 		return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
 				SQLiteAxolotlStore.ACCOUNT + " = ?"
-				+ " AND " + SQLiteAxolotlStore.NAME + " = ?"
-				+ " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR "+SQLiteAxolotlStore.TRUSTED+ " = ?)",
+						+ " AND " + SQLiteAxolotlStore.NAME + " = ?"
+						+ " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
 				args
 		);
 	}
@@ -1018,7 +1022,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
 		int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
 				SQLiteAxolotlStore.ACCOUNT + " = ? AND "
-				+ SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+						+ SQLiteAxolotlStore.FINGERPRINT + " = ? ",
 				selectionArgs);
 		return rows == 1;
 	}
@@ -1036,7 +1040,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	}
 
 	public void recreateAxolotlDb(SQLiteDatabase db) {
-		Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
+		Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
 		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
 		db.execSQL(CREATE_SESSIONS_STATEMENT);
 		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
@@ -1046,12 +1050,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
 		db.execSQL(CREATE_IDENTITIES_STATEMENT);
 	}
-	
+
 	public void wipeAxolotlDb(Account account) {
 		String accountName = account.getUuid();
-		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
+		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
 		SQLiteDatabase db = this.getWritableDatabase();
-		String[] deleteArgs= {
+		String[] deleteArgs = {
 				accountName
 		};
 		db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,

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

@@ -2106,8 +2106,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	}
 
 	public void createContact(Contact contact) {
-		SharedPreferences sharedPref = getPreferences();
-		boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
+		boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true);
 		if (autoGrant) {
 			contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
 			contact.setOption(Contact.Options.ASKING);
@@ -2534,10 +2533,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 				.getDefaultSharedPreferences(getApplicationContext());
 	}
 
-	public boolean forceEncryption() {
-		return getPreferences().getBoolean("force_encryption", false);
-	}
-
 	public boolean confirmMessages() {
 		return getPreferences().getBoolean("confirm_messages", true);
 	}
@@ -2554,6 +2549,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		return getPreferences().getBoolean("indicate_received", false);
 	}
 
+	public boolean useTorToConnect() {
+		return getPreferences().getBoolean("use_tor", false);
+	}
+
 	public int unreadCount() {
 		int count = 0;
 		for (Conversation conversation : getConversations()) {

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

@@ -82,10 +82,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	private ImageButton mRegenerateAxolotlKeyButton;
 	private LinearLayout keys;
 	private LinearLayout keysCard;
+	private LinearLayout mNamePort;
+	private EditText mHostname;
+	private EditText mPort;
 	private AlertDialog mCaptchaDialog = null;
 
 	private Jid jidToEdit;
 	private boolean mInitMode = false;
+	private boolean mUseTor = false;
 	private Account mAccount;
 	private String messageFingerprint;
 
@@ -136,6 +140,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			}
 			final String password = mPassword.getText().toString();
 			final String passwordConfirm = mPasswordConfirm.getText().toString();
+			final String hostname = mHostname.getText().toString();
+			final String port = mPort.getText().toString();
 			if (registerNewAccount) {
 				if (!password.equals(passwordConfirm)) {
 					mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
@@ -149,6 +155,25 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				mPasswordConfirm.setError(null);
 				mAccount.setPassword(password);
 				mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+				if (hostname.contains(" ")) {
+					mHostname.setError(getString(R.string.not_valid_hostname));
+					mHostname.requestFocus();
+					return;
+				}
+				mAccount.setHostname(hostname);
+				try {
+					int numericPort = Integer.parseInt(port);
+					if (numericPort < 0 || numericPort > 65535) {
+						mPort.setError(getString(R.string.not_a_valid_port));
+						mPort.requestFocus();
+						return;
+					}
+					mAccount.setPort(numericPort);
+				} catch (NumberFormatException e) {
+					mPort.setError(getString(R.string.not_a_valid_port));
+					mPort.requestFocus();
+					return;
+				}
 				xmppConnectionService.updateAccount(mAccount);
 			} else {
 				if (xmppConnectionService.findAccountByJid(jid) != null) {
@@ -319,7 +344,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			unmodified = this.mAccount.getJid().toBareJid().toString();
 		}
 		return !unmodified.equals(this.mAccountJid.getText().toString()) ||
-				!this.mAccount.getPassword().equals(this.mPassword.getText().toString());
+				!this.mAccount.getPassword().equals(this.mPassword.getText().toString()) ||
+				!this.mAccount.getHostname().equals(this.mHostname.getText().toString()) ||
+				!String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString());
 	}
 
 	@Override
@@ -368,6 +395,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
 		this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
 		this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
+		this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
+		this.mHostname = (EditText) findViewById(R.id.hostname);
+		this.mHostname.addTextChangedListener(mTextWatcher);
+		this.mPort = (EditText) findViewById(R.id.port);
+		this.mPort.setText("5222");
+		this.mPort.addTextChangedListener(mTextWatcher);
 		this.mSaveButton = (Button) findViewById(R.id.save_button);
 		this.mCancelButton = (Button) findViewById(R.id.cancel_button);
 		this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
@@ -448,6 +481,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				}
 			}
 		}
+		this.mUseTor = getPreferences().getBoolean("use_tor", false);
+		this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
 	}
 
 	@Override
@@ -529,6 +564,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
 			}
 			this.mPassword.setText(this.mAccount.getPassword());
+			this.mHostname.setText("");
+			this.mHostname.getEditableText().append(this.mAccount.getHostname());
+			this.mPort.setText("");
+			this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
+			this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
+
 		}
 		if (!mInitMode) {
 			this.mAvatar.setVisibility(View.VISIBLE);

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

@@ -160,6 +160,8 @@ public class SettingsActivity extends XmppActivity implements
 		} else if (name.equals("dont_trust_system_cas")) {
 			xmppConnectionService.updateMemorizingTrustmanager();
 			reconnectAccounts();
+		} else if (name.equals("use_tor")) {
+			reconnectAccounts();
 		}
 
 	}

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

@@ -27,6 +27,7 @@ import java.net.ConnectException;
 import java.net.IDN;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.Proxy;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.net.URL;
@@ -233,16 +234,23 @@ public class XmppConnection implements Runnable {
 			tagReader = new XmlReader(wakeLock);
 			tagWriter = new TagWriter();
 			this.changeStatus(Account.State.CONNECTING);
+			final boolean useTor = mXmppConnectionService.useTorToConnect();
+			final Proxy TOR_PROXY = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
 			if (DNSHelper.isIp(account.getServer().toString())) {
-				socket = new Socket();
+				socket = useTor ? new Socket(TOR_PROXY) : new Socket();
 				try {
 					socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
 				} catch (IOException e) {
 					throw new UnknownHostException();
 				}
 			} else {
-				final Bundle result = DNSHelper.getSRVRecord(account.getServer(),mXmppConnectionService);
-				final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
+				final ArrayList<Parcelable> values;
+				if (useTor) {
+					values = account.getHostnamePortBundles();
+				} else {
+					final Bundle result = DNSHelper.getSRVRecord(account.getServer(),mXmppConnectionService);
+					values = result.getParcelableArrayList("values");
+				}
 				int i = 0;
 				boolean socketError = true;
 				while (socketError && values.size() > i) {
@@ -269,11 +277,11 @@ public class XmppConnection implements Runnable {
 									+ ": using values from dns "
 									+ srvRecordServer + ":" + srvRecordPort);
 						}
-						socket = new Socket();
+						socket = useTor ? new Socket(TOR_PROXY) : new Socket();
 						socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
 						socketError = false;
 					} catch (final Throwable e) {
-						Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+						Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
 						i++;
 					}
 				}

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java 🔗

@@ -7,7 +7,9 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.Proxy;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.UnknownHostException;
@@ -59,7 +61,9 @@ public class JingleSocks5Transport extends JingleTransport {
 			@Override
 			public void run() {
 				try {
-					socket = new Socket();
+					final boolean useTor = connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
+					final Proxy TOR_PROXY = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
+					socket = useTor ? new Socket(TOR_PROXY) : new Socket();
 					SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
 					socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
 					inputStream = socket.getInputStream();

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

@@ -78,7 +78,59 @@
                     android:textColorHint="@color/black54"
                     android:textSize="?attr/TextSizeBody" />
 
-                <CheckBox
+                    <LinearLayout
+                        android:id="@+id/name_port"
+                        android:layout_marginTop="8dp"
+                        android:orientation="horizontal"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:weightSum="1">
+                        <LinearLayout
+                            android:orientation="vertical"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_weight="0.8">
+                            <TextView
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:text="@string/account_settings_hostname"
+                                android:textColor="@color/black87"
+                                android:textSize="?attr/TextSizeBody"
+                                android:id="@+id/textView"/>
+                            <EditText
+                                android:layout_width="fill_parent"
+                                android:layout_height="wrap_content"
+                                android:textColor="@color/black87"
+                                android:textColorHint="@color/black54"
+                                android:textSize="?attr/TextSizeBody"
+                                android:id="@+id/hostname"
+                                android:inputType="textNoSuggestions"
+                                android:hint="@string/hostname_or_onion"/>
+                        </LinearLayout>
+                        <LinearLayout
+                            android:orientation="vertical"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_weight="0.2"
+                            >
+                            <TextView
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:text="@string/account_settings_port"
+                                android:textColor="@color/black87"
+                                android:textSize="?attr/TextSizeBody"/>
+                            <EditText
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent"
+                                android:inputType="number"
+                                android:maxLength="5"
+                                android:textColor="@color/black87"
+                                android:textColorHint="@color/black54"
+                                android:textSize="?attr/TextSizeBody"
+                                android:id="@+id/port"/>
+                        </LinearLayout>
+                    </LinearLayout>
+                    <CheckBox
                     android:id="@+id/account_register_new"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"

src/main/res/menu/editaccount.xml 🔗

@@ -31,4 +31,9 @@
     <item android:id="@+id/action_clear_devices"
           android:title="@string/clear_other_devices"
           android:showAsAction="never"/>
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/action_settings"/>
 </menu>

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

@@ -540,4 +540,12 @@
 	<string name="error_fetching_omemo_key">Error fetching OMEMO key!</string>
 	<string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string>
 	<string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string>
+	<string name="pref_connection_options">Connection options</string>
+	<string name="pref_use_tor">Connect via Tor</string>
+	<string name="pref_use_tor_summary">Tunnel all connections through the TOR network. Requires Orbot</string>
+	<string name="account_settings_hostname">Hostname</string>
+	<string name="account_settings_port">Port</string>
+	<string name="hostname_or_onion">Server- or .onion-Address</string>
+	<string name="not_a_valid_port">This is not a valid port number</string>
+	<string name="not_valid_hostname">This is not a valid hostname</string>
 </resources>

src/main/res/xml/preferences.xml 🔗

@@ -147,6 +147,13 @@
                     android:summary="@string/pref_remove_trusted_certificates_summary"
                     android:title="@string/pref_remove_trusted_certificates_title"/>
             </PreferenceCategory>
+            <PreferenceCategory android:title="@string/pref_connection_options">
+                <CheckBoxPreference
+                    android:defaultValue="false"
+                    android:key="use_tor"
+                    android:title="@string/pref_use_tor"
+                    android:summary="@string/pref_use_tor_summary"/>
+            </PreferenceCategory>
             <PreferenceCategory android:title="@string/pref_input_options">
                 <CheckBoxPreference
                     android:defaultValue="false"