aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java4
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java50
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java14
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java21
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java11
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java222
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java11
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java43
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java18
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java6
-rw-r--r--src/main/res/layout/activity_edit_account.xml191
-rw-r--r--src/main/res/menu/editaccount.xml5
-rw-r--r--src/main/res/values/strings.xml8
-rw-r--r--src/main/res/xml/preferences.xml7
15 files changed, 421 insertions, 192 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 15530aa1d..75df6e049 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/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 {
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index b02db812c..3aa1675fa 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/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;
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index 90fbadfe3..6435fd477 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/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));
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index 96dee62c7..f371a2551 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/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);
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index 3aa288813..1e4b91021 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/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);
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 0e133d192..8b74581cc 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/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,
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 2c140a83f..ac084758b 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -2113,8 +2113,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);
@@ -2551,10 +2550,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);
}
@@ -2571,6 +2566,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()) {
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index e3f67d826..d4fbc71f3 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/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_omemo_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);
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index eca7c0c14..7118eb5a8 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/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();
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 16b3f78b4..dec3cad6d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/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++;
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index 556395aef..74306ce3d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/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();
diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml
index c803dd20f..e126ade15 100644
--- a/src/main/res/layout/activity_edit_account.xml
+++ b/src/main/res/layout/activity_edit_account.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/grey200">
@@ -10,44 +10,47 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/button_bar"
- android:layout_alignParentTop="true" >
+ android:layout_alignParentTop="true">
<LinearLayout
android:id="@+id/account_main_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical" >
+ android:orientation="vertical">
<RelativeLayout
android:id="@+id/editor"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
android:background="@drawable/infocard_border"
android:orientation="vertical"
android:padding="@dimen/infocard_padding">
+
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/avater"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="16dp"
+ android:adjustViewBounds="true"
+ android:background="@drawable/message_border"
+ android:contentDescription="@string/account_image_description"
android:maxHeight="384dp"
android:maxWidth="384dp"
- android:contentDescription="@string/account_image_description"
- app:riv_corner_radius="2dp"
- android:background="@drawable/message_border"
android:padding="1dp"
- android:adjustViewBounds="true"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="16dp" />
+ app:riv_corner_radius="2dp" />
+
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_below="@+id/avater">
+ android:layout_below="@+id/avater"
+ android:orientation="vertical">
+
<TextView
android:id="@+id/account_jid_label"
android:layout_width="wrap_content"
@@ -84,6 +87,64 @@
android:textColorHint="@color/black54"
android:textSize="?attr/TextSizeBody" />
+ <LinearLayout
+ android:id="@+id/name_port"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:orientation="horizontal"
+ android:weightSum="1">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0.8"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/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" />
+
+ <EditText
+ android:id="@+id/hostname"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/hostname_or_onion"
+ android:inputType="textNoSuggestions"
+ android:textColor="@color/black87"
+ android:textColorHint="@color/black54"
+ android:textSize="?attr/TextSizeBody" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0.2"
+ android:orientation="vertical">
+
+ <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:id="@+id/port"
+ 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" />
+ </LinearLayout>
+ </LinearLayout>
+
<CheckBox
android:id="@+id/account_register_new"
android:layout_width="wrap_content"
@@ -109,10 +170,10 @@
android:layout_marginTop="8dp"
android:hint="@string/confirm_password"
android:inputType="textPassword"
- android:visibility="gone"
android:textColor="@color/black87"
android:textColorHint="@color/black54"
- android:textSize="?attr/TextSizeBody" />
+ android:textSize="?attr/TextSizeBody"
+ android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
@@ -120,19 +181,19 @@
android:id="@+id/stats"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
android:background="@drawable/infocard_border"
android:orientation="vertical"
android:padding="@dimen/infocard_padding"
- android:visibility="gone" >
+ android:visibility="gone">
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:stretchColumns="1" >
+ android:stretchColumns="1">
<TableRow
android:layout_width="fill_parent"
@@ -153,20 +214,21 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
</TableLayout>
+
<TableLayout
android:id="@+id/server_info_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
- android:visibility="gone" >
+ android:visibility="gone">
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -182,12 +244,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -203,12 +265,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -224,12 +286,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -245,12 +307,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -266,12 +328,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -287,12 +349,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -308,11 +370,12 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
+
<TableRow
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
@@ -328,14 +391,14 @@
android:layout_gravity="right"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- tools:ignore="RtlHardcoded"/>
+ tools:ignore="RtlHardcoded" />
</TableRow>
</TableLayout>
<RelativeLayout
+ android:id="@+id/otr_fingerprint_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:id="@+id/otr_fingerprint_box"
android:layout_marginTop="32dp">
<LinearLayout
@@ -349,17 +412,17 @@
android:id="@+id/otr_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:fontFamily="monospace"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- android:typeface="monospace"
- android:fontFamily="monospace"/>
+ android:typeface="monospace" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:text="@string/otr_fingerprint"
android:textColor="@color/black54"
- android:textSize="?attr/TextSizeInfo"
- android:text="@string/otr_fingerprint"/>
+ android:textSize="?attr/TextSizeInfo" />
</LinearLayout>
<ImageButton
@@ -369,15 +432,16 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="?android:selectableItemBackground"
+ android:contentDescription="@string/copy_otr_clipboard_description"
android:padding="@dimen/image_button_padding"
android:src="?attr/icon_copy"
- android:visibility="visible"
- android:contentDescription="@string/copy_otr_clipboard_description"/>
+ android:visibility="visible" />
</RelativeLayout>
+
<RelativeLayout
+ android:id="@+id/axolotl_fingerprint_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:id="@+id/axolotl_fingerprint_box"
android:layout_marginTop="32dp">
<LinearLayout
@@ -391,25 +455,25 @@
android:id="@+id/axolotl_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:fontFamily="monospace"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
- android:typeface="monospace"
- android:fontFamily="monospace"/>
+ android:typeface="monospace" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:text="@string/this_device_omemo_fingerprint"
android:textColor="@color/black54"
- android:textSize="?attr/TextSizeInfo"
- android:text="@string/this_device_omemo_fingerprint"/>
+ android:textSize="?attr/TextSizeInfo" />
</LinearLayout>
<LinearLayout
+ android:id="@+id/axolotl_actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/axolotl_actions"
- android:layout_centerVertical="true"
android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
android:orientation="vertical">
<ImageButton
@@ -417,31 +481,33 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
+ android:contentDescription="@string/copy_omemo_clipboard_description"
android:padding="@dimen/image_button_padding"
android:src="?attr/icon_copy"
- android:visibility="visible"
- android:contentDescription="@string/copy_omemo_clipboard_description"/>
+ android:visibility="visible" />
+
<ImageButton
- android:id="@+id/action_regenerate_omemo_key"
+ android:id="@+id/action_regenerate_axolotl_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
+ android:contentDescription="@string/regenerate_omemo_key"
android:padding="@dimen/image_button_padding"
android:src="?attr/icon_refresh"
- android:visibility="gone"
- android:contentDescription="@string/regenerate_omemo_key"/>
+ android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
+
<LinearLayout
android:id="@+id/other_device_keys_card"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
android:background="@drawable/infocard_border"
android:orientation="vertical"
android:padding="@dimen/infocard_padding"
@@ -451,10 +517,10 @@
android:id="@+id/other_device_keys_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:text="@string/other_devices"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeHeadline"
- android:textStyle="bold"
- android:text="@string/other_devices"/>
+ android:textStyle="bold" />
<LinearLayout
android:id="@+id/other_device_keys"
@@ -462,8 +528,7 @@
android:layout_height="wrap_content"
android:divider="?android:dividerHorizontal"
android:orientation="vertical"
- android:showDividers="middle" >
- </LinearLayout>
+ android:showDividers="middle"></LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
@@ -473,10 +538,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true" >
+ android:layout_alignParentRight="true"
+ android:layout_alignParentStart="true">
<Button
android:id="@+id/cancel_button"
@@ -505,4 +570,4 @@
android:textColor="@color/black54" />
</LinearLayout>
-</RelativeLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml
index 62981a454..b7a404182 100644
--- a/src/main/res/menu/editaccount.xml
+++ b/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> \ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 496711a86..5a33b6e26 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -559,4 +559,12 @@
<string name="pref_plugins_summary">Download plugins for Pix-Art Messenger</string>
<string name="pref_plugin_location">Share-Location</string>
<string name="pref_plugin_location_summary">Send and receive locations</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>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index c09315ba3..c44f960e8 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -167,6 +167,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"