DatabaseBackend.java

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