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}