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