Signal-Android/src/org/thoughtcrime/securesms/database/DatabaseFactory.java

419 lines
18 KiB
Java
Raw Normal View History

2012-09-30 19:56:29 -07:00
/**
2011-12-20 10:20:44 -08:00
* Copyright (C) 2011 Whisper Systems
2012-09-30 19:56:29 -07:00
*
2011-12-20 10:20:44 -08:00
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
2012-09-30 19:56:29 -07:00
*
2011-12-20 10:20:44 -08:00
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
2011-12-20 10:20:44 -08:00
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
2012-09-30 19:56:29 -07:00
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
2012-09-30 19:56:29 -07:00
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.crypto.MasterCipher;
2012-09-30 19:56:29 -07:00
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.util.InvalidMessageException;
2011-12-20 10:20:44 -08:00
public class DatabaseFactory {
2012-09-30 19:56:29 -07:00
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
private static final int INTRODUCED_INDEXES_VERSION = 3;
private static final int INTRODUCED_DATE_SENT_VERSION = 4;
private static final int INTRODUCED_DRAFTS_VERSION = 5;
private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
private static final int DATABASE_VERSION = 6;
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
private static DatabaseFactory instance;
private static EncryptingPartDatabase encryptingPartInstance;
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
private final DatabaseHelper databaseHelper;
private final SmsDatabase sms;
private final EncryptingSmsDatabase encryptingSms;
private final MmsDatabase mms;
private final PartDatabase part;
private final ThreadDatabase thread;
private final CanonicalAddressDatabase address;
private final MmsAddressDatabase mmsAddress;
private final MmsSmsDatabase mmsSmsDatabase;
private final IdentityDatabase identityDatabase;
private final DraftDatabase draftDatabase;
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static DatabaseFactory getInstance(Context context) {
synchronized (lock) {
if (instance == null)
instance = new DatabaseFactory(context);
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
return instance;
}
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static MmsSmsDatabase getMmsSmsDatabase(Context context) {
return getInstance(context).mmsSmsDatabase;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static ThreadDatabase getThreadDatabase(Context context) {
return getInstance(context).thread;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static SmsDatabase getSmsDatabase(Context context) {
return getInstance(context).sms;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static MmsDatabase getMmsDatabase(Context context) {
return getInstance(context).mms;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static CanonicalAddressDatabase getAddressDatabase(Context context) {
return getInstance(context).address;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static EncryptingSmsDatabase getEncryptingSmsDatabase(Context context) {
return getInstance(context).encryptingSms;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static PartDatabase getPartDatabase(Context context) {
return getInstance(context).part;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static EncryptingPartDatabase getEncryptingPartDatabase(Context context, MasterSecret masterSecret) {
synchronized (lock) {
if (encryptingPartInstance == null) {
DatabaseFactory factory = getInstance(context);
2012-09-30 19:56:29 -07:00
encryptingPartInstance = new EncryptingPartDatabase(context, factory.databaseHelper, masterSecret);
2011-12-20 10:20:44 -08:00
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
return encryptingPartInstance;
}
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static MmsAddressDatabase getMmsAddressDatabase(Context context) {
return getInstance(context).mmsAddress;
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public static IdentityDatabase getIdentityDatabase(Context context) {
return getInstance(context).identityDatabase;
}
public static DraftDatabase getDraftDatabase(Context context) {
return getInstance(context).draftDatabase;
}
2011-12-20 10:20:44 -08:00
private DatabaseFactory(Context context) {
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
this.sms = new SmsDatabase(context, databaseHelper);
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
this.mms = new MmsDatabase(context, databaseHelper);
this.part = new PartDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper);
this.address = CanonicalAddressDatabase.getInstance(context);
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper);
2011-12-20 10:20:44 -08:00
}
2012-09-30 19:56:29 -07:00
2011-12-20 10:20:44 -08:00
public void close() {
databaseHelper.close();
address.close();
instance = null;
}
2012-09-30 19:56:29 -07:00
public void onApplicationLevelUpgrade(MasterSecret masterSecret, int fromVersion,
DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
{
if (fromVersion < DatabaseUpgradeActivity.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) {
String KEY_EXCHANGE = "?TextSecureKeyExchange";
String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd";
String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
MasterCipher masterCipher = new MasterCipher(masterSecret);
int count = 0;
SQLiteDatabase db = databaseHelper.getWritableDatabase();
Cursor cursor = db.query("sms",
new String[] {"_id", "type", "body"},
"type & " + 0x80000000 + " != 0",
null, null, null, null);
if (cursor != null)
count = cursor.getCount();
db.beginTransaction();
while (cursor != null && cursor.moveToNext()) {
listener.setProgress(cursor.getPosition(), count);
try {
String body = masterCipher.decryptBody(cursor.getString(cursor.getColumnIndexOrThrow("body")));
long type = cursor.getLong(cursor.getColumnIndexOrThrow("type"));
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
if (body.startsWith(KEY_EXCHANGE)) {
body = body.substring(KEY_EXCHANGE.length());
body = masterCipher.encryptBody(body);
type |= 0x8000;
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
new String[] {body, type+"", id+""});
} else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) {
body = body.substring(PROCESSED_KEY_EXCHANGE.length());
body = masterCipher.encryptBody(body);
type |= 0x2000;
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
new String[] {body, type+"", id+""});
} else if (body.startsWith(STALE_KEY_EXCHANGE)) {
body = body.substring(STALE_KEY_EXCHANGE.length());
body = masterCipher.encryptBody(body);
type |= 0x4000;
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
new String[] {body, type+"", id+""});
}
} catch (InvalidMessageException e) {
Log.w("DatabaseFactory", e);
}
}
db.setTransactionSuccessful();
db.endTransaction();
}
}
2011-12-20 10:20:44 -08:00
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
2011-12-20 10:20:44 -08:00
db.execSQL(SmsDatabase.CREATE_TABLE);
db.execSQL(MmsDatabase.CREATE_TABLE);
db.execSQL(PartDatabase.CREATE_TABLE);
db.execSQL(ThreadDatabase.CREATE_TABLE);
db.execSQL(MmsAddressDatabase.CREATE_TABLE);
db.execSQL(IdentityDatabase.CREATE_TABLE);
db.execSQL(DraftDatabase.CREATE_TABLE);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, PartDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, MmsAddressDatabase.CREATE_INDEXS);
executeStatements(db, DraftDatabase.CREATE_INDEXS);
2011-12-20 10:20:44 -08:00
}
@Override
2012-09-30 19:56:29 -07:00
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < INTRODUCED_IDENTITIES_VERSION) {
db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, key TEXT UNIQUE, name TEXT UNIQUE, mac TEXT);");
}
if (oldVersion < INTRODUCED_INDEXES_VERSION) {
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON sms (thread_id);",
"CREATE INDEX IF NOT EXISTS sms_read_index ON sms (read);",
"CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON sms (read,thread_id);",
"CREATE INDEX IF NOT EXISTS sms_type_index ON sms (type);"
});
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON mms (thread_id);",
"CREATE INDEX IF NOT EXISTS mms_read_index ON mms (read);",
"CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON mms (read,thread_id);",
"CREATE INDEX IF NOT EXISTS mms_message_box_index ON mms (msg_box);"
});
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON part (mid);"
});
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON thread (recipient_ids);",
});
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON mms_addresses (mms_id);",
});
}
if (oldVersion < INTRODUCED_DATE_SENT_VERSION) {
db.beginTransaction();
db.execSQL("ALTER TABLE sms ADD COLUMN date_sent INTEGER;");
db.execSQL("UPDATE sms SET date_sent = date;");
db.execSQL("ALTER TABLE mms ADD COLUMN date_received INTEGER;");
db.execSQL("UPDATE mms SET date_received = date;");
db.setTransactionSuccessful();
db.endTransaction();
}
if (oldVersion < INTRODUCED_DRAFTS_VERSION) {
db.beginTransaction();
db.execSQL("CREATE TABLE drafts (_id INTEGER PRIMARY KEY, thread_id INTEGER, type TEXT, value TEXT);");
executeStatements(db, new String[] {
"CREATE INDEX IF NOT EXISTS draft_thread_index ON drafts (thread_id);",
});
db.setTransactionSuccessful();
db.endTransaction();
}
if (oldVersion < INTRODUCED_NEW_TYPES_VERSION) {
String KEY_EXCHANGE = "?TextSecureKeyExchange";
String SYMMETRIC_ENCRYPT = "?TextSecureLocalEncrypt";
String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt";
String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt";
String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd";
String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
db.beginTransaction();
// SMS Updates
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {20L+"", 1L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {21L+"", 43L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {22L+"", 4L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {23L+"", 2L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {24L+"", 5L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(21L | 0x800000L)+"", 42L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(23L | 0x800000L)+"", 44L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L)+"", 45L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x10000000L)+"", 46L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x20000000L)+"", 47L+""});
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x08000000L)+"", 48L+""});
Cursor cursor = db.query("sms", null,"body LIKE ?", new String[] {SYMMETRIC_ENCRYPT + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, SYMMETRIC_ENCRYPT, 0x80000000L);
cursor = db.query("sms", null,"body LIKE ?", new String[] {ASYMMETRIC_LOCAL_ENCRYPT + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, ASYMMETRIC_LOCAL_ENCRYPT, 0x40000000L);
cursor = db.query("sms", null,"body LIKE ?", new String[] {ASYMMETRIC_ENCRYPT + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, ASYMMETRIC_ENCRYPT, 0L);
cursor = db.query("sms", null,"body LIKE ?", new String[] {KEY_EXCHANGE + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, KEY_EXCHANGE, 0x8000L);
cursor = db.query("sms", null,"body LIKE ?", new String[] {PROCESSED_KEY_EXCHANGE + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, PROCESSED_KEY_EXCHANGE, 0x8000L | 0x2000L);
cursor = db.query("sms", null,"body LIKE ?", new String[] {STALE_KEY_EXCHANGE + "%"},
null, null, null);
updateSmsBodyAndType(db, cursor, STALE_KEY_EXCHANGE, 0x8000L | 0x4000L);
// MMS Updates
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {20L+"", 1+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {23L+"", 2+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {21L+"", 4+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {24L+"", 12+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x800000L) +"", 5+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x800000L) +"", 6+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x20000000L) +"", 7+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L) +"", 8+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x08000000L) +"", 9+""});
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x10000000L) +"", 10+""});
// Thread Updates
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_type INTEGER;");
cursor = db.query("thread", null,"snippet LIKE ?",
new String[] {SYMMETRIC_ENCRYPT + "%"}, null, null, null);
updateThreadSnippetAndType(db, cursor, SYMMETRIC_ENCRYPT, 0x80000000L);
cursor = db.query("thread", null,"snippet LIKE ?",
new String[] {KEY_EXCHANGE + "%"}, null, null, null);
updateThreadSnippetAndType(db, cursor, KEY_EXCHANGE, 0x8000L);
cursor = db.query("thread", null,"snippet LIKE ?",
new String[] {STALE_KEY_EXCHANGE + "%"}, null, null, null);
updateThreadSnippetAndType(db, cursor, STALE_KEY_EXCHANGE, 0x8000L | 0x4000L);
cursor = db.query("thread", null,"snippet LIKE ?",
new String[] {PROCESSED_KEY_EXCHANGE + "%"}, null, null, null);
updateThreadSnippetAndType(db, cursor, KEY_EXCHANGE, 0x8000L | 0x2000L);
db.setTransactionSuccessful();
db.endTransaction();
}
}
private void updateSmsBodyAndType(SQLiteDatabase db, Cursor cursor, String prefix, long typeMask)
{
while (cursor != null && cursor.moveToNext()) {
String body = cursor.getString(cursor.getColumnIndexOrThrow("body"));
long type = cursor.getLong(cursor.getColumnIndexOrThrow("type"));
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
if (body.startsWith(prefix)) {
body = body.substring(prefix.length());
type |= typeMask;
db.execSQL("UPDATE sms SET type = ?, body = ? WHERE _id = ?",
new String[]{type+"", body, id+""});
}
}
if (cursor != null)
cursor.close();
}
private void updateThreadSnippetAndType(SQLiteDatabase db, Cursor cursor, String prefix, long typeMask)
{
while (cursor != null && cursor.moveToNext()) {
String snippet = cursor.getString(cursor.getColumnIndexOrThrow("snippet"));
long type = cursor.getLong(cursor.getColumnIndexOrThrow("snippet_type"));
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
if (snippet.startsWith(prefix)) {
snippet = snippet.substring(prefix.length());
type |= typeMask;
db.execSQL("UPDATE thread SET snippet_type = ?, snippet = ? WHERE _id = ?",
new String[]{type+"", snippet, id+""});
}
}
if (cursor != null)
cursor.close();
}
private void executeStatements(SQLiteDatabase db, String[] statements) {
for (String statement : statements)
db.execSQL(statement);
2011-12-20 10:20:44 -08:00
}
2011-12-20 10:20:44 -08:00
}
}