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