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