DatabaseBackend.java

  1package eu.siacs.conversations.persistance;
  2
  3import android.content.ContentValues;
  4import android.content.Context;
  5import android.database.Cursor;
  6import android.database.sqlite.SQLiteCantOpenDatabaseException;
  7import android.database.sqlite.SQLiteDatabase;
  8import android.database.sqlite.SQLiteOpenHelper;
  9import android.util.Base64;
 10import android.util.Log;
 11
 12import org.whispersystems.libaxolotl.AxolotlAddress;
 13import org.whispersystems.libaxolotl.state.PreKeyRecord;
 14import org.whispersystems.libaxolotl.state.SessionRecord;
 15import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
 16
 17import java.io.IOException;
 18import java.util.ArrayList;
 19import java.util.List;
 20import java.util.concurrent.CopyOnWriteArrayList;
 21
 22import eu.siacs.conversations.Config;
 23import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 24import eu.siacs.conversations.entities.Account;
 25import eu.siacs.conversations.entities.Contact;
 26import eu.siacs.conversations.entities.Conversation;
 27import eu.siacs.conversations.entities.Message;
 28import eu.siacs.conversations.entities.Roster;
 29import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 30import eu.siacs.conversations.xmpp.jid.Jid;
 31
 32public class DatabaseBackend extends SQLiteOpenHelper {
 33
 34	private static DatabaseBackend instance = null;
 35
 36	private static final String DATABASE_NAME = "history";
 37	private static final int DATABASE_VERSION = 15;
 38
 39	private static String CREATE_CONTATCS_STATEMENT = "create table "
 40			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
 41			+ Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
 42			+ Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
 43			+ Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
 44			+ Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
 45			+ Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
 46			+ Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
 47			+ Account.TABLENAME + "(" + Account.UUID
 48			+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
 49			+ Contact.JID + ") ON CONFLICT REPLACE);";
 50
 51	private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
 52			+ AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "("
 53				+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 54				+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
 55				+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 56					+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
 57				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 58				+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
 59					+ AxolotlService.SQLiteAxolotlStore.ID
 60				+ ") ON CONFLICT REPLACE"
 61			+");";
 62
 63	private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
 64			+ AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
 65				+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 66				+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
 67				+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 68					+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
 69				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 70				+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
 71					+ AxolotlService.SQLiteAxolotlStore.ID
 72				+ ") ON CONFLICT REPLACE"+
 73			");";
 74
 75	private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
 76			+ AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "("
 77				+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
 78				+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
 79				+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
 80				+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
 81				+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
 82					+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
 83				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
 84				+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
 85					+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
 86					+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID
 87				+ ") ON CONFLICT REPLACE"
 88			+");";
 89
 90	private DatabaseBackend(Context context) {
 91		super(context, DATABASE_NAME, null, DATABASE_VERSION);
 92	}
 93
 94	@Override
 95	public void onCreate(SQLiteDatabase db) {
 96		db.execSQL("PRAGMA foreign_keys=ON;");
 97		db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID
 98				+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
 99				+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
100				+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
101				+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
102				+ " TEXT)");
103		db.execSQL("create table " + Conversation.TABLENAME + " ("
104				+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
105				+ " TEXT, " + Conversation.CONTACT + " TEXT, "
106				+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
107				+ " TEXT, " + Conversation.CREATED + " NUMBER, "
108				+ Conversation.STATUS + " NUMBER, " + Conversation.MODE
109				+ " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
110				+ Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
111				+ "(" + Account.UUID + ") ON DELETE CASCADE);");
112		db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
113				+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
114				+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
115				+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
116				+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
117				+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
118				+ Message.RELATIVE_FILE_PATH + " TEXT, "
119				+ Message.SERVER_MSG_ID + " TEXT, "
120				+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
121				+ Message.CONVERSATION + ") REFERENCES "
122				+ Conversation.TABLENAME + "(" + Conversation.UUID
123				+ ") ON DELETE CASCADE);");
124
125		db.execSQL(CREATE_CONTATCS_STATEMENT);
126	}
127
128	@Override
129	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
130		if (oldVersion < 2 && newVersion >= 2) {
131			db.execSQL("update " + Account.TABLENAME + " set "
132					+ Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
133		}
134		if (oldVersion < 3 && newVersion >= 3) {
135			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
136					+ Message.TYPE + " NUMBER");
137		}
138		if (oldVersion < 5 && newVersion >= 5) {
139			db.execSQL("DROP TABLE " + Contact.TABLENAME);
140			db.execSQL(CREATE_CONTATCS_STATEMENT);
141			db.execSQL("UPDATE " + Account.TABLENAME + " SET "
142					+ Account.ROSTERVERSION + " = NULL");
143		}
144		if (oldVersion < 6 && newVersion >= 6) {
145			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
146					+ Message.TRUE_COUNTERPART + " TEXT");
147		}
148		if (oldVersion < 7 && newVersion >= 7) {
149			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
150					+ Message.REMOTE_MSG_ID + " TEXT");
151			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
152					+ Contact.AVATAR + " TEXT");
153			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
154					+ Account.AVATAR + " TEXT");
155		}
156		if (oldVersion < 8 && newVersion >= 8) {
157			db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
158					+ Conversation.ATTRIBUTES + " TEXT");
159		}
160		if (oldVersion < 9 && newVersion >= 9) {
161			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
162					+ Contact.LAST_TIME + " NUMBER");
163			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
164					+ Contact.LAST_PRESENCE + " TEXT");
165		}
166		if (oldVersion < 10 && newVersion >= 10) {
167			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
168					+ Message.RELATIVE_FILE_PATH + " TEXT");
169		}
170		if (oldVersion < 11 && newVersion >= 11) {
171			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
172					+ Contact.GROUPS + " TEXT");
173			db.execSQL("delete from "+Contact.TABLENAME);
174			db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL");
175		}
176		if (oldVersion < 12 && newVersion >= 12) {
177			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
178					+ Message.SERVER_MSG_ID + " TEXT");
179		}
180		if (oldVersion < 13 && newVersion >= 13) {
181			db.execSQL("delete from "+Contact.TABLENAME);
182			db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL");
183		}
184		if (oldVersion < 14 && newVersion >= 14) {
185			// migrate db to new, canonicalized JID domainpart representation
186
187			// Conversation table
188			Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
189			while(cursor.moveToNext()) {
190				String newJid;
191				try {
192					newJid = Jid.fromString(
193							cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
194					).toString();
195				} catch (InvalidJidException ignored) {
196					Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
197							+cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
198							+": " + ignored +". Skipping...");
199					continue;
200				}
201
202				String updateArgs[] = {
203						newJid,
204						cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
205				};
206				db.execSQL("update " + Conversation.TABLENAME
207						+ " set " + Conversation.CONTACTJID	+ " = ? "
208						+ " where " + Conversation.UUID + " = ?", updateArgs);
209			}
210			cursor.close();
211
212			// Contact table
213			cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
214			while(cursor.moveToNext()) {
215				String newJid;
216				try {
217					newJid = Jid.fromString(
218							cursor.getString(cursor.getColumnIndex(Contact.JID))
219					).toString();
220				} catch (InvalidJidException ignored) {
221					Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
222							+cursor.getString(cursor.getColumnIndex(Contact.JID))
223							+": " + ignored +". Skipping...");
224					continue;
225				}
226
227				String updateArgs[] = {
228						newJid,
229						cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
230						cursor.getString(cursor.getColumnIndex(Contact.JID)),
231				};
232				db.execSQL("update " + Contact.TABLENAME
233						+ " set " + Contact.JID + " = ? "
234						+ " where " + Contact.ACCOUNT + " = ? "
235						+ " AND " + Contact.JID + " = ?", updateArgs);
236			}
237			cursor.close();
238
239			// Account table
240			cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
241			while(cursor.moveToNext()) {
242				String newServer;
243				try {
244					newServer = Jid.fromParts(
245							cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
246							cursor.getString(cursor.getColumnIndex(Account.SERVER)),
247							"mobile"
248					).getDomainpart();
249				} catch (InvalidJidException ignored) {
250					Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
251							+cursor.getString(cursor.getColumnIndex(Account.SERVER))
252							+": " + ignored +". Skipping...");
253					continue;
254				}
255
256				String updateArgs[] = {
257						newServer,
258						cursor.getString(cursor.getColumnIndex(Account.UUID)),
259				};
260				db.execSQL("update " + Account.TABLENAME
261						+ " set " + Account.SERVER + " = ? "
262						+ " where " + Account.UUID + " = ?", updateArgs);
263			}
264			cursor.close();
265		}
266		if (oldVersion < 15  && newVersion >= 15) {
267			db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME);
268			db.execSQL(CREATE_SESSIONS_STATEMENT);
269			db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME);
270			db.execSQL(CREATE_PREKEYS_STATEMENT);
271			db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
272			db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
273		}
274	}
275
276	public static synchronized DatabaseBackend getInstance(Context context) {
277		if (instance == null) {
278			instance = new DatabaseBackend(context);
279		}
280		return instance;
281	}
282
283	public void createConversation(Conversation conversation) {
284		SQLiteDatabase db = this.getWritableDatabase();
285		db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
286	}
287
288	public void createMessage(Message message) {
289		SQLiteDatabase db = this.getWritableDatabase();
290		db.insert(Message.TABLENAME, null, message.getContentValues());
291	}
292
293	public void createAccount(Account account) {
294		SQLiteDatabase db = this.getWritableDatabase();
295		db.insert(Account.TABLENAME, null, account.getContentValues());
296	}
297
298	public void createContact(Contact contact) {
299		SQLiteDatabase db = this.getWritableDatabase();
300		db.insert(Contact.TABLENAME, null, contact.getContentValues());
301	}
302
303	public int getConversationCount() {
304		SQLiteDatabase db = this.getReadableDatabase();
305		Cursor cursor = db.rawQuery("select count(uuid) as count from "
306				+ Conversation.TABLENAME + " where " + Conversation.STATUS
307				+ "=" + Conversation.STATUS_AVAILABLE, null);
308		cursor.moveToFirst();
309		int count = cursor.getInt(0);
310		cursor.close();
311		return count;
312	}
313
314	public CopyOnWriteArrayList<Conversation> getConversations(int status) {
315		CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
316		SQLiteDatabase db = this.getReadableDatabase();
317		String[] selectionArgs = { Integer.toString(status) };
318		Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
319				+ " where " + Conversation.STATUS + " = ? order by "
320				+ Conversation.CREATED + " desc", selectionArgs);
321		while (cursor.moveToNext()) {
322			list.add(Conversation.fromCursor(cursor));
323		}
324		cursor.close();
325		return list;
326	}
327
328	public ArrayList<Message> getMessages(Conversation conversations, int limit) {
329		return getMessages(conversations, limit, -1);
330	}
331
332	public ArrayList<Message> getMessages(Conversation conversation, int limit,
333			long timestamp) {
334		ArrayList<Message> list = new ArrayList<>();
335		SQLiteDatabase db = this.getReadableDatabase();
336		Cursor cursor;
337		if (timestamp == -1) {
338			String[] selectionArgs = { conversation.getUuid() };
339			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
340					+ "=?", selectionArgs, null, null, Message.TIME_SENT
341					+ " DESC", String.valueOf(limit));
342		} else {
343			String[] selectionArgs = { conversation.getUuid(),
344					Long.toString(timestamp) };
345			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
346					+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
347					null, null, Message.TIME_SENT + " DESC",
348					String.valueOf(limit));
349		}
350		if (cursor.getCount() > 0) {
351			cursor.moveToLast();
352			do {
353				Message message = Message.fromCursor(cursor);
354				message.setConversation(conversation);
355				list.add(message);
356			} while (cursor.moveToPrevious());
357		}
358		cursor.close();
359		return list;
360	}
361
362	public Conversation findConversation(final Account account, final Jid contactJid) {
363		SQLiteDatabase db = this.getReadableDatabase();
364		String[] selectionArgs = { account.getUuid(),
365				contactJid.toBareJid().toString() + "/%",
366				contactJid.toBareJid().toString()
367				};
368		Cursor cursor = db.query(Conversation.TABLENAME, null,
369				Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
370						+ " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
371		if (cursor.getCount() == 0)
372			return null;
373		cursor.moveToFirst();
374		Conversation conversation = Conversation.fromCursor(cursor);
375		cursor.close();
376		return conversation;
377	}
378
379	public void updateConversation(final Conversation conversation) {
380		final SQLiteDatabase db = this.getWritableDatabase();
381		final String[] args = { conversation.getUuid() };
382		db.update(Conversation.TABLENAME, conversation.getContentValues(),
383				Conversation.UUID + "=?", args);
384	}
385
386	public List<Account> getAccounts() {
387		List<Account> list = new ArrayList<>();
388		SQLiteDatabase db = this.getReadableDatabase();
389		Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
390				null, null);
391		while (cursor.moveToNext()) {
392			list.add(Account.fromCursor(cursor));
393		}
394		cursor.close();
395		return list;
396	}
397
398	public void updateAccount(Account account) {
399		SQLiteDatabase db = this.getWritableDatabase();
400		String[] args = { account.getUuid() };
401		db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
402				+ "=?", args);
403	}
404
405	public void deleteAccount(Account account) {
406		SQLiteDatabase db = this.getWritableDatabase();
407		String[] args = { account.getUuid() };
408		db.delete(Account.TABLENAME, Account.UUID + "=?", args);
409	}
410
411	public boolean hasEnabledAccounts() {
412		SQLiteDatabase db = this.getReadableDatabase();
413		Cursor cursor = db.rawQuery("select count(" + Account.UUID + ")  from "
414				+ Account.TABLENAME + " where not options & (1 <<1)", null);
415		try {
416			cursor.moveToFirst();
417			int count = cursor.getInt(0);
418			cursor.close();
419			return (count > 0);
420		} catch (SQLiteCantOpenDatabaseException e) {
421			return true; // better safe than sorry
422		} catch (RuntimeException e) {
423			return true; // better safe than sorry
424		}
425	}
426
427	@Override
428	public SQLiteDatabase getWritableDatabase() {
429		SQLiteDatabase db = super.getWritableDatabase();
430		db.execSQL("PRAGMA foreign_keys=ON;");
431		return db;
432	}
433
434	public void updateMessage(Message message) {
435		SQLiteDatabase db = this.getWritableDatabase();
436		String[] args = { message.getUuid() };
437		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
438				+ "=?", args);
439	}
440
441	public void readRoster(Roster roster) {
442		SQLiteDatabase db = this.getReadableDatabase();
443		Cursor cursor;
444		String args[] = { roster.getAccount().getUuid() };
445		cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
446		while (cursor.moveToNext()) {
447			roster.initContact(Contact.fromCursor(cursor));
448		}
449		cursor.close();
450	}
451
452	public void writeRoster(final Roster roster) {
453		final Account account = roster.getAccount();
454		final SQLiteDatabase db = this.getWritableDatabase();
455		for (Contact contact : roster.getContacts()) {
456			if (contact.getOption(Contact.Options.IN_ROSTER)) {
457				db.insert(Contact.TABLENAME, null, contact.getContentValues());
458			} else {
459				String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
460				String[] whereArgs = { account.getUuid(), contact.getJid().toString() };
461				db.delete(Contact.TABLENAME, where, whereArgs);
462			}
463		}
464		account.setRosterVersion(roster.getVersion());
465		updateAccount(account);
466	}
467
468	public void deleteMessage(Message message) {
469		SQLiteDatabase db = this.getWritableDatabase();
470		String[] args = { message.getUuid() };
471		db.delete(Message.TABLENAME, Message.UUID + "=?", args);
472	}
473
474	public void deleteMessagesInConversation(Conversation conversation) {
475		SQLiteDatabase db = this.getWritableDatabase();
476		String[] args = { conversation.getUuid() };
477		db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
478	}
479
480	public Conversation findConversationByUuid(String conversationUuid) {
481		SQLiteDatabase db = this.getReadableDatabase();
482		String[] selectionArgs = { conversationUuid };
483		Cursor cursor = db.query(Conversation.TABLENAME, null,
484				Conversation.UUID + "=?", selectionArgs, null, null, null);
485		if (cursor.getCount() == 0) {
486			return null;
487		}
488		cursor.moveToFirst();
489		Conversation conversation = Conversation.fromCursor(cursor);
490		cursor.close();
491		return conversation;
492	}
493
494	public Message findMessageByUuid(String messageUuid) {
495		SQLiteDatabase db = this.getReadableDatabase();
496		String[] selectionArgs = { messageUuid };
497		Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
498				selectionArgs, null, null, null);
499		if (cursor.getCount() == 0) {
500			return null;
501		}
502		cursor.moveToFirst();
503		Message message = Message.fromCursor(cursor);
504		cursor.close();
505		return message;
506	}
507
508	public Account findAccountByUuid(String accountUuid) {
509		SQLiteDatabase db = this.getReadableDatabase();
510		String[] selectionArgs = { accountUuid };
511		Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
512				selectionArgs, null, null, null);
513		if (cursor.getCount() == 0) {
514			return null;
515		}
516		cursor.moveToFirst();
517		Account account = Account.fromCursor(cursor);
518		cursor.close();
519		return account;
520	}
521
522	public List<Message> getImageMessages(Conversation conversation) {
523		ArrayList<Message> list = new ArrayList<>();
524		SQLiteDatabase db = this.getReadableDatabase();
525		Cursor cursor;
526			String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) };
527			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
528					+ "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null);
529		if (cursor.getCount() > 0) {
530			cursor.moveToLast();
531			do {
532				Message message = Message.fromCursor(cursor);
533				message.setConversation(conversation);
534				list.add(message);
535			} while (cursor.moveToPrevious());
536		}
537		cursor.close();
538		return list;
539	}
540
541	private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
542		final SQLiteDatabase db = this.getReadableDatabase();
543		String[] columns = null;
544		String[] selectionArgs = {account.getUuid(),
545				contact.getName(),
546				Integer.toString(contact.getDeviceId())};
547		Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
548				columns,
549				AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
550						+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
551						+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
552				selectionArgs,
553				null, null, null);
554
555		return cursor;
556	}
557
558	public SessionRecord loadSession(Account account, AxolotlAddress contact) {
559		SessionRecord session = null;
560		Cursor cursor = getCursorForSession(account, contact);
561		if(cursor.getCount() != 0) {
562			cursor.moveToFirst();
563			try {
564				session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
565			} catch (IOException e) {
566				throw new AssertionError(e);
567			}
568		}
569		cursor.close();
570		return session;
571	}
572
573	public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
574		List<Integer> devices = new ArrayList<>();
575		final SQLiteDatabase db = this.getReadableDatabase();
576		String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID};
577		String[] selectionArgs = {account.getUuid(),
578				contact.getName()};
579		Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
580				columns,
581				AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
582						+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND ",
583				selectionArgs,
584				null, null, null);
585
586		while(cursor.moveToNext()) {
587			devices.add(cursor.getInt(
588					cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID)));
589		}
590
591		cursor.close();
592		return devices;
593	}
594
595	public boolean containsSession(Account account, AxolotlAddress contact) {
596		Cursor cursor = getCursorForSession(account, contact);
597		int count = cursor.getCount();
598		cursor.close();
599		return count != 0;
600	}
601
602	public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
603		SQLiteDatabase db = this.getWritableDatabase();
604		ContentValues values = new ContentValues();
605		values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName());
606		values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
607		values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
608		values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
609		db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
610	}
611
612	public void deleteSession(Account account, AxolotlAddress contact) {
613		SQLiteDatabase db = this.getWritableDatabase();
614		String[] args = {account.getUuid(),
615				contact.getName(),
616				Integer.toString(contact.getDeviceId())};
617		db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
618				AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
619						+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
620						+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
621				args);
622	}
623
624	public void deleteAllSessions(Account account, AxolotlAddress contact) {
625		SQLiteDatabase db = this.getWritableDatabase();
626		String[] args = {account.getUuid(), contact.getName()};
627		db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
628				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
629						+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
630				args);
631	}
632
633	public boolean isTrustedSession(Account account, AxolotlAddress contact) {
634		boolean trusted = false;
635		Cursor cursor = getCursorForSession(account, contact);
636		if(cursor.getCount() != 0) {
637			cursor.moveToFirst();
638			trusted = cursor.getInt(cursor.getColumnIndex(
639					AxolotlService.SQLiteAxolotlStore.TRUSTED)) > 0;
640		}
641		cursor.close();
642		return trusted;
643	}
644
645	public void setTrustedSession(Account account, AxolotlAddress contact, boolean trusted) {
646		SQLiteDatabase db = this.getWritableDatabase();
647		ContentValues values = new ContentValues();
648		values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName());
649		values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
650		values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
651		values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted?1:0);
652		db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
653	}
654
655	private Cursor getCursorForPreKey(Account account, int preKeyId) {
656		SQLiteDatabase db = this.getReadableDatabase();
657		String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
658		String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
659		Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
660				columns,
661				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
662						+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
663				selectionArgs,
664				null, null, null);
665
666		return cursor;
667	}
668
669	public PreKeyRecord loadPreKey(Account account, int preKeyId) {
670		PreKeyRecord record = null;
671		Cursor cursor = getCursorForPreKey(account, preKeyId);
672		if(cursor.getCount() != 0) {
673			cursor.moveToFirst();
674			try {
675				record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
676			} catch (IOException e ) {
677				throw new AssertionError(e);
678			}
679		}
680		cursor.close();
681		return record;
682	}
683
684	public boolean containsPreKey(Account account, int preKeyId) {
685		Cursor cursor = getCursorForPreKey(account, preKeyId);
686		int count = cursor.getCount();
687		cursor.close();
688		return count != 0;
689	}
690
691	public void storePreKey(Account account, PreKeyRecord record) {
692		SQLiteDatabase db = this.getWritableDatabase();
693		ContentValues values = new ContentValues();
694		values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
695		values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
696		values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
697		db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
698	}
699
700	public void deletePreKey(Account account, int preKeyId) {
701		SQLiteDatabase db = this.getWritableDatabase();
702		String[] args = {account.getUuid(), Integer.toString(preKeyId)};
703		db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
704				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
705						+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
706				args);
707	}
708
709	private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
710		SQLiteDatabase db = this.getReadableDatabase();
711		String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
712		String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
713		Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
714				columns,
715				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?",
716				selectionArgs,
717				null, null, null);
718
719		return cursor;
720	}
721
722	public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
723		SignedPreKeyRecord record = null;
724		Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
725		if(cursor.getCount() != 0) {
726			cursor.moveToFirst();
727			try {
728				record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
729			} catch (IOException e ) {
730				throw new AssertionError(e);
731			}
732		}
733		cursor.close();
734		return record;
735	}
736
737	public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
738		List<SignedPreKeyRecord> prekeys = new ArrayList<>();
739		SQLiteDatabase db = this.getReadableDatabase();
740		String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
741		String[] selectionArgs = {account.getUuid()};
742		Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
743				columns,
744				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?",
745				selectionArgs,
746				null, null, null);
747
748		while(cursor.moveToNext()) {
749			try {
750				prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
751			} catch (IOException ignored) {
752			}
753		}
754		return prekeys;
755	}
756
757	public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
758		Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
759		int count = cursor.getCount();
760		cursor.close();
761		return count != 0;
762	}
763
764	public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
765		SQLiteDatabase db = this.getWritableDatabase();
766		ContentValues values = new ContentValues();
767		values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
768		values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
769		values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
770		db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
771	}
772
773	public void deleteSignedPreKey(Account account, int signedPreKeyId) {
774		SQLiteDatabase db = this.getWritableDatabase();
775		String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
776		db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
777				AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
778						+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
779				args);
780	}
781}