DatabaseBackend.java

   1package eu.siacs.conversations.persistance;
   2
   3import android.content.ContentValues;
   4import android.content.Context;
   5import android.database.Cursor;
   6import android.database.DatabaseUtils;
   7import android.database.sqlite.SQLiteCantOpenDatabaseException;
   8import android.database.sqlite.SQLiteDatabase;
   9import android.database.sqlite.SQLiteOpenHelper;
  10import android.util.Base64;
  11import android.util.Log;
  12import android.util.Pair;
  13
  14import org.whispersystems.libaxolotl.AxolotlAddress;
  15import org.whispersystems.libaxolotl.IdentityKey;
  16import org.whispersystems.libaxolotl.IdentityKeyPair;
  17import org.whispersystems.libaxolotl.InvalidKeyException;
  18import org.whispersystems.libaxolotl.state.PreKeyRecord;
  19import org.whispersystems.libaxolotl.state.SessionRecord;
  20import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
  21
  22import java.io.ByteArrayInputStream;
  23import java.io.IOException;
  24import java.security.cert.CertificateEncodingException;
  25import java.security.cert.CertificateException;
  26import java.security.cert.CertificateFactory;
  27import java.security.cert.X509Certificate;
  28import java.util.ArrayList;
  29import java.util.HashSet;
  30import java.util.Iterator;
  31import java.util.List;
  32import java.util.Set;
  33import java.util.concurrent.CopyOnWriteArrayList;
  34import org.json.JSONException;
  35
  36import eu.siacs.conversations.Config;
  37import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  38import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
  39import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
  40import eu.siacs.conversations.entities.Account;
  41import eu.siacs.conversations.entities.Contact;
  42import eu.siacs.conversations.entities.Conversation;
  43import eu.siacs.conversations.entities.Message;
  44import eu.siacs.conversations.entities.PresenceTemplate;
  45import eu.siacs.conversations.entities.Roster;
  46import eu.siacs.conversations.entities.ServiceDiscoveryResult;
  47import eu.siacs.conversations.xmpp.jid.InvalidJidException;
  48import eu.siacs.conversations.xmpp.jid.Jid;
  49
  50public class DatabaseBackend extends SQLiteOpenHelper {
  51
  52	private static DatabaseBackend instance = null;
  53
  54	private static final String DATABASE_NAME = "history";
  55	private static final int DATABASE_VERSION = 26;
  56
  57	private static String CREATE_CONTATCS_STATEMENT = "create table "
  58			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
  59			+ Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
  60			+ Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
  61			+ Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
  62			+ Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
  63			+ Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
  64			+ Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
  65			+ Account.TABLENAME + "(" + Account.UUID
  66			+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
  67			+ Contact.JID + ") ON CONFLICT REPLACE);";
  68
  69	private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
  70			+ ServiceDiscoveryResult.TABLENAME + "("
  71			+ ServiceDiscoveryResult.HASH + " TEXT, "
  72			+ ServiceDiscoveryResult.VER + " TEXT, "
  73			+ ServiceDiscoveryResult.RESULT + " TEXT, "
  74			+ "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
  75			+ ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
  76
  77	private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE "
  78			+ PresenceTemplate.TABELNAME + "("
  79			+ PresenceTemplate.UUID + " TEXT, "
  80			+ PresenceTemplate.LAST_USED + " NUMBER,"
  81			+ PresenceTemplate.MESSAGE + " TEXT,"
  82			+ PresenceTemplate.STATUS + " TEXT,"
  83			+ "UNIQUE("+PresenceTemplate.MESSAGE + "," +PresenceTemplate.STATUS+") ON CONFLICT REPLACE);";
  84
  85	private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
  86			+ SQLiteAxolotlStore.PREKEY_TABLENAME + "("
  87			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
  88			+ SQLiteAxolotlStore.ID + " INTEGER, "
  89			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
  90			+ SQLiteAxolotlStore.ACCOUNT
  91			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
  92			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
  93			+ SQLiteAxolotlStore.ID
  94			+ ") ON CONFLICT REPLACE"
  95			+ ");";
  96
  97	private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
  98			+ SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
  99			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 100			+ SQLiteAxolotlStore.ID + " INTEGER, "
 101			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 102			+ SQLiteAxolotlStore.ACCOUNT
 103			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 104			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
 105			+ SQLiteAxolotlStore.ID
 106			+ ") ON CONFLICT REPLACE" +
 107			");";
 108
 109	private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
 110			+ SQLiteAxolotlStore.SESSION_TABLENAME + "("
 111			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 112			+ SQLiteAxolotlStore.NAME + " TEXT, "
 113			+ SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
 114			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 115			+ SQLiteAxolotlStore.ACCOUNT
 116			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 117			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
 118			+ SQLiteAxolotlStore.NAME + ", "
 119			+ SQLiteAxolotlStore.DEVICE_ID
 120			+ ") ON CONFLICT REPLACE"
 121			+ ");";
 122
 123	private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
 124			+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
 125			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 126			+ SQLiteAxolotlStore.NAME + " TEXT, "
 127			+ SQLiteAxolotlStore.OWN + " INTEGER, "
 128			+ SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
 129			+ SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
 130			+ SQLiteAxolotlStore.TRUSTED + " INTEGER, "
 131			+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 132			+ SQLiteAxolotlStore.ACCOUNT
 133			+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 134			+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
 135			+ SQLiteAxolotlStore.NAME + ", "
 136			+ SQLiteAxolotlStore.FINGERPRINT
 137			+ ") ON CONFLICT IGNORE"
 138			+ ");";
 139
 140	private DatabaseBackend(Context context) {
 141		super(context, DATABASE_NAME, null, DATABASE_VERSION);
 142	}
 143
 144	@Override
 145	public void onCreate(SQLiteDatabase db) {
 146		db.execSQL("PRAGMA foreign_keys=ON;");
 147		db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID
 148				+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
 149				+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
 150				+ Account.DISPLAY_NAME + " TEXT, "
 151				+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
 152				+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
 153				+ " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)");
 154		db.execSQL("create table " + Conversation.TABLENAME + " ("
 155				+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
 156				+ " TEXT, " + Conversation.CONTACT + " TEXT, "
 157				+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
 158				+ " TEXT, " + Conversation.CREATED + " NUMBER, "
 159				+ Conversation.STATUS + " NUMBER, " + Conversation.MODE
 160				+ " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
 161				+ Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
 162				+ "(" + Account.UUID + ") ON DELETE CASCADE);");
 163		db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
 164				+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
 165				+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
 166				+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
 167				+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
 168				+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
 169				+ Message.RELATIVE_FILE_PATH + " TEXT, "
 170				+ Message.SERVER_MSG_ID + " TEXT, "
 171				+ Message.FINGERPRINT + " TEXT, "
 172				+ Message.CARBON + " INTEGER, "
 173				+ Message.EDITED + " TEXT, "
 174				+ Message.READ + " NUMBER DEFAULT 1, "
 175				+ Message.OOB + " INTEGER, "
 176				+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
 177				+ Message.CONVERSATION + ") REFERENCES "
 178				+ Conversation.TABLENAME + "(" + Conversation.UUID
 179				+ ") ON DELETE CASCADE);");
 180
 181		db.execSQL(CREATE_CONTATCS_STATEMENT);
 182		db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
 183		db.execSQL(CREATE_SESSIONS_STATEMENT);
 184		db.execSQL(CREATE_PREKEYS_STATEMENT);
 185		db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
 186		db.execSQL(CREATE_IDENTITIES_STATEMENT);
 187		db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
 188	}
 189
 190	@Override
 191	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 192		if (oldVersion < 2 && newVersion >= 2) {
 193			db.execSQL("update " + Account.TABLENAME + " set "
 194					+ Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
 195		}
 196		if (oldVersion < 3 && newVersion >= 3) {
 197			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 198					+ Message.TYPE + " NUMBER");
 199		}
 200		if (oldVersion < 5 && newVersion >= 5) {
 201			db.execSQL("DROP TABLE " + Contact.TABLENAME);
 202			db.execSQL(CREATE_CONTATCS_STATEMENT);
 203			db.execSQL("UPDATE " + Account.TABLENAME + " SET "
 204					+ Account.ROSTERVERSION + " = NULL");
 205		}
 206		if (oldVersion < 6 && newVersion >= 6) {
 207			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 208					+ Message.TRUE_COUNTERPART + " TEXT");
 209		}
 210		if (oldVersion < 7 && newVersion >= 7) {
 211			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 212					+ Message.REMOTE_MSG_ID + " TEXT");
 213			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 214					+ Contact.AVATAR + " TEXT");
 215			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
 216					+ Account.AVATAR + " TEXT");
 217		}
 218		if (oldVersion < 8 && newVersion >= 8) {
 219			db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
 220					+ Conversation.ATTRIBUTES + " TEXT");
 221		}
 222		if (oldVersion < 9 && newVersion >= 9) {
 223			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 224					+ Contact.LAST_TIME + " NUMBER");
 225			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 226					+ Contact.LAST_PRESENCE + " TEXT");
 227		}
 228		if (oldVersion < 10 && newVersion >= 10) {
 229			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 230					+ Message.RELATIVE_FILE_PATH + " TEXT");
 231		}
 232		if (oldVersion < 11 && newVersion >= 11) {
 233			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 234					+ Contact.GROUPS + " TEXT");
 235			db.execSQL("delete from " + Contact.TABLENAME);
 236			db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
 237		}
 238		if (oldVersion < 12 && newVersion >= 12) {
 239			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 240					+ Message.SERVER_MSG_ID + " TEXT");
 241		}
 242		if (oldVersion < 13 && newVersion >= 13) {
 243			db.execSQL("delete from " + Contact.TABLENAME);
 244			db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
 245		}
 246		if (oldVersion < 14 && newVersion >= 14) {
 247			// migrate db to new, canonicalized JID domainpart representation
 248
 249			// Conversation table
 250			Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
 251			while (cursor.moveToNext()) {
 252				String newJid;
 253				try {
 254					newJid = Jid.fromString(
 255							cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
 256					).toString();
 257				} catch (InvalidJidException ignored) {
 258					Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
 259							+ cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
 260							+ ": " + ignored + ". Skipping...");
 261					continue;
 262				}
 263
 264				String updateArgs[] = {
 265						newJid,
 266						cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
 267				};
 268				db.execSQL("update " + Conversation.TABLENAME
 269						+ " set " + Conversation.CONTACTJID + " = ? "
 270						+ " where " + Conversation.UUID + " = ?", updateArgs);
 271			}
 272			cursor.close();
 273
 274			// Contact table
 275			cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
 276			while (cursor.moveToNext()) {
 277				String newJid;
 278				try {
 279					newJid = Jid.fromString(
 280							cursor.getString(cursor.getColumnIndex(Contact.JID))
 281					).toString();
 282				} catch (InvalidJidException ignored) {
 283					Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
 284							+ cursor.getString(cursor.getColumnIndex(Contact.JID))
 285							+ ": " + ignored + ". Skipping...");
 286					continue;
 287				}
 288
 289				String updateArgs[] = {
 290						newJid,
 291						cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
 292						cursor.getString(cursor.getColumnIndex(Contact.JID)),
 293				};
 294				db.execSQL("update " + Contact.TABLENAME
 295						+ " set " + Contact.JID + " = ? "
 296						+ " where " + Contact.ACCOUNT + " = ? "
 297						+ " AND " + Contact.JID + " = ?", updateArgs);
 298			}
 299			cursor.close();
 300
 301			// Account table
 302			cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
 303			while (cursor.moveToNext()) {
 304				String newServer;
 305				try {
 306					newServer = Jid.fromParts(
 307							cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
 308							cursor.getString(cursor.getColumnIndex(Account.SERVER)),
 309							"mobile"
 310					).getDomainpart();
 311				} catch (InvalidJidException ignored) {
 312					Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
 313							+ cursor.getString(cursor.getColumnIndex(Account.SERVER))
 314							+ ": " + ignored + ". Skipping...");
 315					continue;
 316				}
 317
 318				String updateArgs[] = {
 319						newServer,
 320						cursor.getString(cursor.getColumnIndex(Account.UUID)),
 321				};
 322				db.execSQL("update " + Account.TABLENAME
 323						+ " set " + Account.SERVER + " = ? "
 324						+ " where " + Account.UUID + " = ?", updateArgs);
 325			}
 326			cursor.close();
 327		}
 328		if (oldVersion < 15 && newVersion >= 15) {
 329			recreateAxolotlDb(db);
 330			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 331					+ Message.FINGERPRINT + " TEXT");
 332		}
 333		if (oldVersion < 16 && newVersion >= 16) {
 334			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 335					+ Message.CARBON + " INTEGER");
 336		}
 337		if (oldVersion < 19 && newVersion >= 19) {
 338			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
 339		}
 340		if (oldVersion < 20 && newVersion >= 20) {
 341			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
 342			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
 343		}
 344		if (oldVersion < 26 && newVersion >= 26) {
 345			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
 346			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
 347		}
 348		/* Any migrations that alter the Account table need to happen BEFORE this migration, as it
 349		 * depends on account de-serialization.
 350		 */
 351		if (oldVersion < 17 && newVersion >= 17) {
 352			List<Account> accounts = getAccounts(db);
 353			for (Account account : accounts) {
 354				String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
 355				if (ownDeviceIdString == null) {
 356					continue;
 357				}
 358				int ownDeviceId = Integer.valueOf(ownDeviceIdString);
 359				AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId);
 360				deleteSession(db, account, ownAddress);
 361				IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
 362				if (identityKeyPair != null) {
 363					setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED);
 364				} else {
 365					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
 366				}
 367			}
 368		}
 369		if (oldVersion < 18 && newVersion >= 18) {
 370			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
 371		}
 372
 373		if (oldVersion < 21 && newVersion >= 21) {
 374			List<Account> accounts = getAccounts(db);
 375			for (Account account : accounts) {
 376				account.unsetPgpSignature();
 377				db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
 378						+ "=?", new String[]{account.getUuid()});
 379			}
 380		}
 381
 382		if (oldVersion < 22 && newVersion >= 22) {
 383			db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
 384		}
 385
 386		if (oldVersion < 23 && newVersion >= 23) {
 387			db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
 388		}
 389
 390		if (oldVersion < 24 && newVersion >= 24) {
 391			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
 392		}
 393
 394		if (oldVersion < 25 && newVersion >= 25) {
 395			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
 396		}
 397
 398		if (oldVersion <  26 && newVersion >= 26) {
 399			db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
 400		}
 401	}
 402
 403	public static synchronized DatabaseBackend getInstance(Context context) {
 404		if (instance == null) {
 405			instance = new DatabaseBackend(context);
 406		}
 407		return instance;
 408	}
 409
 410	public void createConversation(Conversation conversation) {
 411		SQLiteDatabase db = this.getWritableDatabase();
 412		db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
 413	}
 414
 415	public void createMessage(Message message) {
 416		SQLiteDatabase db = this.getWritableDatabase();
 417		db.insert(Message.TABLENAME, null, message.getContentValues());
 418	}
 419
 420	public void createAccount(Account account) {
 421		SQLiteDatabase db = this.getWritableDatabase();
 422		db.insert(Account.TABLENAME, null, account.getContentValues());
 423	}
 424
 425	public void insertDiscoveryResult(ServiceDiscoveryResult result) {
 426		SQLiteDatabase db = this.getWritableDatabase();
 427		db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
 428	}
 429
 430	public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
 431		SQLiteDatabase db = this.getReadableDatabase();
 432		String[] selectionArgs = {hash, ver};
 433		Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
 434				ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
 435				selectionArgs, null, null, null);
 436		if (cursor.getCount() == 0) {
 437			cursor.close();
 438			return null;
 439		}
 440		cursor.moveToFirst();
 441
 442		ServiceDiscoveryResult result = null;
 443		try {
 444			result = new ServiceDiscoveryResult(cursor);
 445		} catch (JSONException e) { /* result is still null */ }
 446
 447		cursor.close();
 448		return result;
 449	}
 450
 451	public void insertPresenceTemplate(PresenceTemplate template) {
 452		SQLiteDatabase db = this.getWritableDatabase();
 453		db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues());
 454	}
 455
 456	public List<PresenceTemplate> getPresenceTemplates() {
 457		ArrayList<PresenceTemplate> templates = new ArrayList<>();
 458		SQLiteDatabase db = this.getReadableDatabase();
 459		Cursor cursor = db.query(PresenceTemplate.TABELNAME,null,null,null,null,null,PresenceTemplate.LAST_USED+" desc");
 460		while (cursor.moveToNext()) {
 461			templates.add(PresenceTemplate.fromCursor(cursor));
 462		}
 463		cursor.close();
 464		return templates;
 465	}
 466
 467	public void deletePresenceTemplate(PresenceTemplate template) {
 468		Log.d(Config.LOGTAG,"deleting presence template with uuid "+template.getUuid());
 469		SQLiteDatabase db = this.getWritableDatabase();
 470		String where = PresenceTemplate.UUID+"=?";
 471		String[] whereArgs = {template.getUuid()};
 472		db.delete(PresenceTemplate.TABELNAME,where,whereArgs);
 473	}
 474
 475	public CopyOnWriteArrayList<Conversation> getConversations(int status) {
 476		CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
 477		SQLiteDatabase db = this.getReadableDatabase();
 478		String[] selectionArgs = {Integer.toString(status)};
 479		Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
 480				+ " where " + Conversation.STATUS + " = ? order by "
 481				+ Conversation.CREATED + " desc", selectionArgs);
 482		while (cursor.moveToNext()) {
 483			list.add(Conversation.fromCursor(cursor));
 484		}
 485		cursor.close();
 486		return list;
 487	}
 488
 489	public ArrayList<Message> getMessages(Conversation conversations, int limit) {
 490		return getMessages(conversations, limit, -1);
 491	}
 492
 493	public ArrayList<Message> getMessages(Conversation conversation, int limit,
 494										  long timestamp) {
 495		ArrayList<Message> list = new ArrayList<>();
 496		SQLiteDatabase db = this.getReadableDatabase();
 497		Cursor cursor;
 498		if (timestamp == -1) {
 499			String[] selectionArgs = {conversation.getUuid()};
 500			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
 501					+ "=?", selectionArgs, null, null, Message.TIME_SENT
 502					+ " DESC", String.valueOf(limit));
 503		} else {
 504			String[] selectionArgs = {conversation.getUuid(),
 505					Long.toString(timestamp)};
 506			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
 507							+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
 508					null, null, Message.TIME_SENT + " DESC",
 509					String.valueOf(limit));
 510		}
 511		if (cursor.getCount() > 0) {
 512			cursor.moveToLast();
 513			do {
 514				Message message = Message.fromCursor(cursor);
 515				message.setConversation(conversation);
 516				list.add(message);
 517			} while (cursor.moveToPrevious());
 518		}
 519		cursor.close();
 520		return list;
 521	}
 522
 523	public Iterable<Message> getMessagesIterable(final Conversation conversation) {
 524		return new Iterable<Message>() {
 525			@Override
 526			public Iterator<Message> iterator() {
 527				class MessageIterator implements Iterator<Message> {
 528					SQLiteDatabase db = getReadableDatabase();
 529					String[] selectionArgs = {conversation.getUuid()};
 530					Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
 531							+ "=?", selectionArgs, null, null, Message.TIME_SENT
 532							+ " ASC", null);
 533
 534					public MessageIterator() {
 535						cursor.moveToFirst();
 536					}
 537
 538					@Override
 539					public boolean hasNext() {
 540						return !cursor.isAfterLast();
 541					}
 542
 543					@Override
 544					public Message next() {
 545						Message message = Message.fromCursor(cursor);
 546						cursor.moveToNext();
 547						return message;
 548					}
 549
 550					@Override
 551					public void remove() {
 552						throw new UnsupportedOperationException();
 553					}
 554				}
 555				return new MessageIterator();
 556			}
 557		};
 558	}
 559
 560	public Conversation findConversation(final Account account, final Jid contactJid) {
 561		SQLiteDatabase db = this.getReadableDatabase();
 562		String[] selectionArgs = {account.getUuid(),
 563				contactJid.toBareJid().toString() + "/%",
 564				contactJid.toBareJid().toString()
 565		};
 566		Cursor cursor = db.query(Conversation.TABLENAME, null,
 567				Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
 568						+ " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
 569		if (cursor.getCount() == 0) {
 570			cursor.close();
 571			return null;
 572		}
 573		cursor.moveToFirst();
 574		Conversation conversation = Conversation.fromCursor(cursor);
 575		cursor.close();
 576		return conversation;
 577	}
 578
 579	public void updateConversation(final Conversation conversation) {
 580		final SQLiteDatabase db = this.getWritableDatabase();
 581		final String[] args = {conversation.getUuid()};
 582		db.update(Conversation.TABLENAME, conversation.getContentValues(),
 583				Conversation.UUID + "=?", args);
 584	}
 585
 586	public List<Account> getAccounts() {
 587		SQLiteDatabase db = this.getReadableDatabase();
 588		return getAccounts(db);
 589	}
 590
 591	private List<Account> getAccounts(SQLiteDatabase db) {
 592		List<Account> list = new ArrayList<>();
 593		Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
 594				null, null);
 595		while (cursor.moveToNext()) {
 596			list.add(Account.fromCursor(cursor));
 597		}
 598		cursor.close();
 599		return list;
 600	}
 601
 602	public void updateAccount(Account account) {
 603		SQLiteDatabase db = this.getWritableDatabase();
 604		String[] args = {account.getUuid()};
 605		db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
 606				+ "=?", args);
 607	}
 608
 609	public void deleteAccount(Account account) {
 610		SQLiteDatabase db = this.getWritableDatabase();
 611		String[] args = {account.getUuid()};
 612		db.delete(Account.TABLENAME, Account.UUID + "=?", args);
 613	}
 614
 615	public boolean hasEnabledAccounts() {
 616		SQLiteDatabase db = this.getReadableDatabase();
 617		Cursor cursor = db.rawQuery("select count(" + Account.UUID + ")  from "
 618				+ Account.TABLENAME + " where not options & (1 <<1)", null);
 619		try {
 620			cursor.moveToFirst();
 621			int count = cursor.getInt(0);
 622			return (count > 0);
 623		} catch (SQLiteCantOpenDatabaseException e) {
 624			return true; // better safe than sorry
 625		} catch (RuntimeException e) {
 626			return true; // better safe than sorry
 627		} finally {
 628			if (cursor != null) {
 629				cursor.close();
 630			}
 631		}
 632	}
 633
 634	@Override
 635	public SQLiteDatabase getWritableDatabase() {
 636		SQLiteDatabase db = super.getWritableDatabase();
 637		db.execSQL("PRAGMA foreign_keys=ON;");
 638		return db;
 639	}
 640
 641	public void updateMessage(Message message) {
 642		SQLiteDatabase db = this.getWritableDatabase();
 643		String[] args = {message.getUuid()};
 644		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
 645				+ "=?", args);
 646	}
 647
 648	public void updateMessage(Message message, String uuid) {
 649		SQLiteDatabase db = this.getWritableDatabase();
 650		String[] args = {uuid};
 651		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
 652				+ "=?", args);
 653	}
 654
 655	public void readRoster(Roster roster) {
 656		SQLiteDatabase db = this.getReadableDatabase();
 657		Cursor cursor;
 658		String args[] = {roster.getAccount().getUuid()};
 659		cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
 660		while (cursor.moveToNext()) {
 661			roster.initContact(Contact.fromCursor(cursor));
 662		}
 663		cursor.close();
 664	}
 665
 666	public void writeRoster(final Roster roster) {
 667		final Account account = roster.getAccount();
 668		final SQLiteDatabase db = this.getWritableDatabase();
 669		db.beginTransaction();
 670		for (Contact contact : roster.getContacts()) {
 671			if (contact.getOption(Contact.Options.IN_ROSTER)) {
 672				db.insert(Contact.TABLENAME, null, contact.getContentValues());
 673			} else {
 674				String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
 675				String[] whereArgs = {account.getUuid(), contact.getJid().toString()};
 676				db.delete(Contact.TABLENAME, where, whereArgs);
 677			}
 678		}
 679		db.setTransactionSuccessful();
 680		db.endTransaction();
 681		account.setRosterVersion(roster.getVersion());
 682		updateAccount(account);
 683	}
 684
 685	public void deleteMessagesInConversation(Conversation conversation) {
 686		SQLiteDatabase db = this.getWritableDatabase();
 687		String[] args = {conversation.getUuid()};
 688		db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
 689	}
 690
 691	public Pair<Long, String> getLastMessageReceived(Account account) {
 692		Cursor cursor = null;
 693		try {
 694			SQLiteDatabase db = this.getReadableDatabase();
 695			String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
 696			String[] args = {account.getUuid()};
 697			cursor = db.rawQuery(sql, args);
 698			if (cursor.getCount() == 0) {
 699				return null;
 700			} else {
 701				cursor.moveToFirst();
 702				return new Pair<>(cursor.getLong(0), cursor.getString(1));
 703			}
 704		} catch (Exception e) {
 705			return null;
 706		} finally {
 707			if (cursor != null) {
 708				cursor.close();
 709			}
 710		}
 711	}
 712
 713	private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
 714		final SQLiteDatabase db = this.getReadableDatabase();
 715		String[] columns = null;
 716		String[] selectionArgs = {account.getUuid(),
 717				contact.getName(),
 718				Integer.toString(contact.getDeviceId())};
 719		Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
 720				columns,
 721				SQLiteAxolotlStore.ACCOUNT + " = ? AND "
 722						+ SQLiteAxolotlStore.NAME + " = ? AND "
 723						+ SQLiteAxolotlStore.DEVICE_ID + " = ? ",
 724				selectionArgs,
 725				null, null, null);
 726
 727		return cursor;
 728	}
 729
 730	public SessionRecord loadSession(Account account, AxolotlAddress contact) {
 731		SessionRecord session = null;
 732		Cursor cursor = getCursorForSession(account, contact);
 733		if (cursor.getCount() != 0) {
 734			cursor.moveToFirst();
 735			try {
 736				session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
 737			} catch (IOException e) {
 738				cursor.close();
 739				throw new AssertionError(e);
 740			}
 741		}
 742		cursor.close();
 743		return session;
 744	}
 745
 746	public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
 747		final SQLiteDatabase db = this.getReadableDatabase();
 748		return getSubDeviceSessions(db, account, contact);
 749	}
 750
 751	private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
 752		List<Integer> devices = new ArrayList<>();
 753		String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
 754		String[] selectionArgs = {account.getUuid(),
 755				contact.getName()};
 756		Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
 757				columns,
 758				SQLiteAxolotlStore.ACCOUNT + " = ? AND "
 759						+ SQLiteAxolotlStore.NAME + " = ?",
 760				selectionArgs,
 761				null, null, null);
 762
 763		while (cursor.moveToNext()) {
 764			devices.add(cursor.getInt(
 765					cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
 766		}
 767
 768		cursor.close();
 769		return devices;
 770	}
 771
 772	public boolean containsSession(Account account, AxolotlAddress contact) {
 773		Cursor cursor = getCursorForSession(account, contact);
 774		int count = cursor.getCount();
 775		cursor.close();
 776		return count != 0;
 777	}
 778
 779	public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
 780		SQLiteDatabase db = this.getWritableDatabase();
 781		ContentValues values = new ContentValues();
 782		values.put(SQLiteAxolotlStore.NAME, contact.getName());
 783		values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
 784		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
 785		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 786		db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
 787	}
 788
 789	public void deleteSession(Account account, AxolotlAddress contact) {
 790		SQLiteDatabase db = this.getWritableDatabase();
 791		deleteSession(db, account, contact);
 792	}
 793
 794	private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
 795		String[] args = {account.getUuid(),
 796				contact.getName(),
 797				Integer.toString(contact.getDeviceId())};
 798		db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
 799				SQLiteAxolotlStore.ACCOUNT + " = ? AND "
 800						+ SQLiteAxolotlStore.NAME + " = ? AND "
 801						+ SQLiteAxolotlStore.DEVICE_ID + " = ? ",
 802				args);
 803	}
 804
 805	public void deleteAllSessions(Account account, AxolotlAddress contact) {
 806		SQLiteDatabase db = this.getWritableDatabase();
 807		String[] args = {account.getUuid(), contact.getName()};
 808		db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
 809				SQLiteAxolotlStore.ACCOUNT + "=? AND "
 810						+ SQLiteAxolotlStore.NAME + " = ?",
 811				args);
 812	}
 813
 814	private Cursor getCursorForPreKey(Account account, int preKeyId) {
 815		SQLiteDatabase db = this.getReadableDatabase();
 816		String[] columns = {SQLiteAxolotlStore.KEY};
 817		String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
 818		Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
 819				columns,
 820				SQLiteAxolotlStore.ACCOUNT + "=? AND "
 821						+ SQLiteAxolotlStore.ID + "=?",
 822				selectionArgs,
 823				null, null, null);
 824
 825		return cursor;
 826	}
 827
 828	public PreKeyRecord loadPreKey(Account account, int preKeyId) {
 829		PreKeyRecord record = null;
 830		Cursor cursor = getCursorForPreKey(account, preKeyId);
 831		if (cursor.getCount() != 0) {
 832			cursor.moveToFirst();
 833			try {
 834				record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
 835			} catch (IOException e) {
 836				throw new AssertionError(e);
 837			}
 838		}
 839		cursor.close();
 840		return record;
 841	}
 842
 843	public boolean containsPreKey(Account account, int preKeyId) {
 844		Cursor cursor = getCursorForPreKey(account, preKeyId);
 845		int count = cursor.getCount();
 846		cursor.close();
 847		return count != 0;
 848	}
 849
 850	public void storePreKey(Account account, PreKeyRecord record) {
 851		SQLiteDatabase db = this.getWritableDatabase();
 852		ContentValues values = new ContentValues();
 853		values.put(SQLiteAxolotlStore.ID, record.getId());
 854		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
 855		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 856		db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
 857	}
 858
 859	public void deletePreKey(Account account, int preKeyId) {
 860		SQLiteDatabase db = this.getWritableDatabase();
 861		String[] args = {account.getUuid(), Integer.toString(preKeyId)};
 862		db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
 863				SQLiteAxolotlStore.ACCOUNT + "=? AND "
 864						+ SQLiteAxolotlStore.ID + "=?",
 865				args);
 866	}
 867
 868	private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
 869		SQLiteDatabase db = this.getReadableDatabase();
 870		String[] columns = {SQLiteAxolotlStore.KEY};
 871		String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
 872		Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
 873				columns,
 874				SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
 875				selectionArgs,
 876				null, null, null);
 877
 878		return cursor;
 879	}
 880
 881	public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
 882		SignedPreKeyRecord record = null;
 883		Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
 884		if (cursor.getCount() != 0) {
 885			cursor.moveToFirst();
 886			try {
 887				record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
 888			} catch (IOException e) {
 889				throw new AssertionError(e);
 890			}
 891		}
 892		cursor.close();
 893		return record;
 894	}
 895
 896	public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
 897		List<SignedPreKeyRecord> prekeys = new ArrayList<>();
 898		SQLiteDatabase db = this.getReadableDatabase();
 899		String[] columns = {SQLiteAxolotlStore.KEY};
 900		String[] selectionArgs = {account.getUuid()};
 901		Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
 902				columns,
 903				SQLiteAxolotlStore.ACCOUNT + "=?",
 904				selectionArgs,
 905				null, null, null);
 906
 907		while (cursor.moveToNext()) {
 908			try {
 909				prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
 910			} catch (IOException ignored) {
 911			}
 912		}
 913		cursor.close();
 914		return prekeys;
 915	}
 916
 917	public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
 918		Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
 919		int count = cursor.getCount();
 920		cursor.close();
 921		return count != 0;
 922	}
 923
 924	public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
 925		SQLiteDatabase db = this.getWritableDatabase();
 926		ContentValues values = new ContentValues();
 927		values.put(SQLiteAxolotlStore.ID, record.getId());
 928		values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
 929		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
 930		db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
 931	}
 932
 933	public void deleteSignedPreKey(Account account, int signedPreKeyId) {
 934		SQLiteDatabase db = this.getWritableDatabase();
 935		String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
 936		db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
 937				SQLiteAxolotlStore.ACCOUNT + "=? AND "
 938						+ SQLiteAxolotlStore.ID + "=?",
 939				args);
 940	}
 941
 942	private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
 943		final SQLiteDatabase db = this.getReadableDatabase();
 944		return getIdentityKeyCursor(db, account, name, own);
 945	}
 946
 947	private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
 948		return getIdentityKeyCursor(db, account, name, own, null);
 949	}
 950
 951	private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
 952		final SQLiteDatabase db = this.getReadableDatabase();
 953		return getIdentityKeyCursor(db, account, fingerprint);
 954	}
 955
 956	private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
 957		return getIdentityKeyCursor(db, account, null, null, fingerprint);
 958	}
 959
 960	private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
 961		String[] columns = {SQLiteAxolotlStore.TRUSTED,
 962				SQLiteAxolotlStore.KEY};
 963		ArrayList<String> selectionArgs = new ArrayList<>(4);
 964		selectionArgs.add(account.getUuid());
 965		String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
 966		if (name != null) {
 967			selectionArgs.add(name);
 968			selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
 969		}
 970		if (fingerprint != null) {
 971			selectionArgs.add(fingerprint);
 972			selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
 973		}
 974		if (own != null) {
 975			selectionArgs.add(own ? "1" : "0");
 976			selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
 977		}
 978		Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
 979				columns,
 980				selectionString,
 981				selectionArgs.toArray(new String[selectionArgs.size()]),
 982				null, null, null);
 983
 984		return cursor;
 985	}
 986
 987	public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
 988		SQLiteDatabase db = getReadableDatabase();
 989		return loadOwnIdentityKeyPair(db, account);
 990	}
 991
 992	private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
 993		String name = account.getJid().toBareJid().toString();
 994		IdentityKeyPair identityKeyPair = null;
 995		Cursor cursor = getIdentityKeyCursor(db, account, name, true);
 996		if (cursor.getCount() != 0) {
 997			cursor.moveToFirst();
 998			try {
 999				identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
1000			} catch (InvalidKeyException e) {
1001				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
1002			}
1003		}
1004		cursor.close();
1005
1006		return identityKeyPair;
1007	}
1008
1009	public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
1010		return loadIdentityKeys(account, name, null);
1011	}
1012
1013	public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
1014		Set<IdentityKey> identityKeys = new HashSet<>();
1015		Cursor cursor = getIdentityKeyCursor(account, name, false);
1016
1017		while (cursor.moveToNext()) {
1018			if (trust != null &&
1019					cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
1020							!= trust.getCode()) {
1021				continue;
1022			}
1023			try {
1024				identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
1025			} catch (InvalidKeyException e) {
1026				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
1027			}
1028		}
1029		cursor.close();
1030
1031		return identityKeys;
1032	}
1033
1034	public long numTrustedKeys(Account account, String name) {
1035		SQLiteDatabase db = getReadableDatabase();
1036		String[] args = {
1037				account.getUuid(),
1038				name,
1039				String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()),
1040				String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode())
1041		};
1042		return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1043				SQLiteAxolotlStore.ACCOUNT + " = ?"
1044						+ " AND " + SQLiteAxolotlStore.NAME + " = ?"
1045						+ " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
1046				args
1047		);
1048	}
1049
1050	private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
1051		storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
1052	}
1053
1054	private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
1055		SQLiteDatabase db = this.getWritableDatabase();
1056		ContentValues values = new ContentValues();
1057		values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
1058		values.put(SQLiteAxolotlStore.NAME, name);
1059		values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
1060		values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
1061		values.put(SQLiteAxolotlStore.KEY, base64Serialized);
1062		values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
1063		db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
1064	}
1065
1066	public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
1067		Cursor cursor = getIdentityKeyCursor(account, fingerprint);
1068		XmppAxolotlSession.Trust trust = null;
1069		if (cursor.getCount() > 0) {
1070			cursor.moveToFirst();
1071			int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
1072			trust = XmppAxolotlSession.Trust.fromCode(trustValue);
1073		}
1074		cursor.close();
1075		return trust;
1076	}
1077
1078	public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1079		SQLiteDatabase db = this.getWritableDatabase();
1080		return setIdentityKeyTrust(db, account, fingerprint, trust);
1081	}
1082
1083	private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1084		String[] selectionArgs = {
1085				account.getUuid(),
1086				fingerprint
1087		};
1088		ContentValues values = new ContentValues();
1089		values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
1090		int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1091				SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1092						+ SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1093				selectionArgs);
1094		return rows == 1;
1095	}
1096
1097	public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
1098		SQLiteDatabase db = this.getWritableDatabase();
1099		String[] selectionArgs = {
1100				account.getUuid(),
1101				fingerprint
1102		};
1103		try {
1104			ContentValues values = new ContentValues();
1105			values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
1106			return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1107					SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1108							+ SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1109					selectionArgs) == 1;
1110		} catch (CertificateEncodingException e) {
1111			Log.d(Config.LOGTAG, "could not encode certificate");
1112			return false;
1113		}
1114	}
1115
1116	public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
1117		SQLiteDatabase db = this.getReadableDatabase();
1118		String[] selectionArgs = {
1119				account.getUuid(),
1120				fingerprint
1121		};
1122		String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
1123		String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
1124		Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
1125		if (cursor.getCount() < 1) {
1126			return null;
1127		} else {
1128			cursor.moveToFirst();
1129			byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
1130			if (certificate == null || certificate.length == 0) {
1131				return null;
1132			}
1133			try {
1134				CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1135				return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
1136			} catch (CertificateException e) {
1137				Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
1138				return null;
1139			}
1140		}
1141	}
1142
1143	public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
1144		storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
1145	}
1146
1147	public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
1148		storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
1149	}
1150
1151	public void recreateAxolotlDb() {
1152		recreateAxolotlDb(getWritableDatabase());
1153	}
1154
1155	public void recreateAxolotlDb(SQLiteDatabase db) {
1156		Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
1157		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
1158		db.execSQL(CREATE_SESSIONS_STATEMENT);
1159		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
1160		db.execSQL(CREATE_PREKEYS_STATEMENT);
1161		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
1162		db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
1163		db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
1164		db.execSQL(CREATE_IDENTITIES_STATEMENT);
1165	}
1166
1167	public void wipeAxolotlDb(Account account) {
1168		String accountName = account.getUuid();
1169		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
1170		SQLiteDatabase db = this.getWritableDatabase();
1171		String[] deleteArgs = {
1172				accountName
1173		};
1174		db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
1175				SQLiteAxolotlStore.ACCOUNT + " = ?",
1176				deleteArgs);
1177		db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
1178				SQLiteAxolotlStore.ACCOUNT + " = ?",
1179				deleteArgs);
1180		db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
1181				SQLiteAxolotlStore.ACCOUNT + " = ?",
1182				deleteArgs);
1183		db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1184				SQLiteAxolotlStore.ACCOUNT + " = ?",
1185				deleteArgs);
1186	}
1187}