initial tor support

This commit is contained in:
Daniel Gultsch 2015-11-28 20:11:38 +01:00
parent 06cadab7cc
commit f0b1761ec3
15 changed files with 346 additions and 130 deletions

View File

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

View File

@ -2,6 +2,8 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import eu.siacs.conversations.crypto.PgpDecryptionService; import eu.siacs.conversations.crypto.PgpDecryptionService;
@ -13,6 +15,7 @@ import org.json.JSONObject;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey; import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@ -39,6 +42,8 @@ public class Account extends AbstractEntity {
public static final String KEYS = "keys"; public static final String KEYS = "keys";
public static final String AVATAR = "avatar"; public static final String AVATAR = "avatar";
public static final String DISPLAY_NAME = "display_name"; 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"; 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, DISABLED,
OFFLINE, OFFLINE,
CONNECTING, CONNECTING,
@ -147,6 +165,8 @@ public class Account extends AbstractEntity {
protected JSONObject keys = new JSONObject(); protected JSONObject keys = new JSONObject();
protected String avatar; protected String avatar;
protected String displayName = null; protected String displayName = null;
protected String hostname = null;
protected int port = 5222;
protected boolean online = false; protected boolean online = false;
private OtrService mOtrService = null; private OtrService mOtrService = null;
private AxolotlService axolotlService = null; private AxolotlService axolotlService = null;
@ -164,12 +184,12 @@ public class Account extends AbstractEntity {
public Account(final Jid jid, final String password) { public Account(final Jid jid, final String password) {
this(java.util.UUID.randomUUID().toString(), jid, 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, public Account(final String uuid, final Jid jid,
final String password, final int options, final String rosterVersion, final String keys, 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.uuid = uuid;
this.jid = jid; this.jid = jid;
if (jid.isBareJid()) { if (jid.isBareJid()) {
@ -185,6 +205,8 @@ public class Account extends AbstractEntity {
} }
this.avatar = avatar; this.avatar = avatar;
this.displayName = displayName; this.displayName = displayName;
this.hostname = hostname;
this.port = port;
} }
public static Account fromCursor(final Cursor cursor) { 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(ROSTERVERSION)),
cursor.getString(cursor.getColumnIndex(KEYS)), cursor.getString(cursor.getColumnIndex(KEYS)),
cursor.getString(cursor.getColumnIndex(AVATAR)), 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) { public boolean isOptionSet(final int option) {
@ -236,6 +260,22 @@ public class Account extends AbstractEntity {
this.password = password; 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() { public State getStatus() {
if (isOptionSet(OPTION_DISABLED)) { if (isOptionSet(OPTION_DISABLED)) {
return State.DISABLED; return State.DISABLED;
@ -314,6 +354,8 @@ public class Account extends AbstractEntity {
values.put(ROSTERVERSION, rosterVersion); values.put(ROSTERVERSION, rosterVersion);
values.put(AVATAR, avatar); values.put(AVATAR, avatar);
values.put(DISPLAY_NAME, displayName); values.put(DISPLAY_NAME, displayName);
values.put(HOSTNAME, hostname);
values.put(PORT, port);
return values; return values;
} }

View File

@ -1,7 +1,13 @@
package eu.siacs.conversations.http; package eu.siacs.conversations.http;
import android.os.Build;
import org.apache.http.conn.ssl.StrictHostnameVerifier; 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.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
@ -87,4 +93,12 @@ public class HttpConnectionManager extends AbstractConnectionManager {
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } 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));
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -82,10 +82,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private ImageButton mRegenerateAxolotlKeyButton; private ImageButton mRegenerateAxolotlKeyButton;
private LinearLayout keys; private LinearLayout keys;
private LinearLayout keysCard; private LinearLayout keysCard;
private LinearLayout mNamePort;
private EditText mHostname;
private EditText mPort;
private AlertDialog mCaptchaDialog = null; private AlertDialog mCaptchaDialog = null;
private Jid jidToEdit; private Jid jidToEdit;
private boolean mInitMode = false; private boolean mInitMode = false;
private boolean mUseTor = false;
private Account mAccount; private Account mAccount;
private String messageFingerprint; private String messageFingerprint;
@ -136,6 +140,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} }
final String password = mPassword.getText().toString(); final String password = mPassword.getText().toString();
final String passwordConfirm = mPasswordConfirm.getText().toString(); final String passwordConfirm = mPasswordConfirm.getText().toString();
final String hostname = mHostname.getText().toString();
final String port = mPort.getText().toString();
if (registerNewAccount) { if (registerNewAccount) {
if (!password.equals(passwordConfirm)) { if (!password.equals(passwordConfirm)) {
mPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
@ -149,6 +155,25 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
mPasswordConfirm.setError(null); mPasswordConfirm.setError(null);
mAccount.setPassword(password); mAccount.setPassword(password);
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); 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); xmppConnectionService.updateAccount(mAccount);
} else { } else {
if (xmppConnectionService.findAccountByJid(jid) != null) { if (xmppConnectionService.findAccountByJid(jid) != null) {
@ -319,7 +344,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
unmodified = this.mAccount.getJid().toBareJid().toString(); unmodified = this.mAccount.getJid().toBareJid().toString();
} }
return !unmodified.equals(this.mAccountJid.getText().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 @Override
@ -368,6 +395,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
this.keys = (LinearLayout) findViewById(R.id.other_device_keys); 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.mSaveButton = (Button) findViewById(R.id.save_button);
this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button);
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); 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 @Override
@ -529,6 +564,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
} }
this.mPassword.setText(this.mAccount.getPassword()); 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) { if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setVisibility(View.VISIBLE);

View File

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

View File

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

View File

@ -7,7 +7,9 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -59,7 +61,9 @@ public class JingleSocks5Transport extends JingleTransport {
@Override @Override
public void run() { public void run() {
try { 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()); SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
socket.connect(address,Config.SOCKET_TIMEOUT * 1000); socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();

View File

@ -78,7 +78,59 @@
android:textColorHint="@color/black54" android:textColorHint="@color/black54"
android:textSize="?attr/TextSizeBody" /> 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:id="@+id/account_register_new"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

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

View File

@ -540,4 +540,12 @@
<string name="error_fetching_omemo_key">Error fetching OMEMO key!</string> <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="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="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> </resources>

View File

@ -147,6 +147,13 @@
android:summary="@string/pref_remove_trusted_certificates_summary" android:summary="@string/pref_remove_trusted_certificates_summary"
android:title="@string/pref_remove_trusted_certificates_title"/> android:title="@string/pref_remove_trusted_certificates_title"/>
</PreferenceCategory> </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"> <PreferenceCategory android:title="@string/pref_input_options">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"