2018-01-24 19:17:44 -08:00
package org.thoughtcrime.securesms.database.helpers ;
2018-03-18 15:06:51 -07:00
import android.content.ContentValues ;
2018-01-24 19:17:44 -08:00
import android.content.Context ;
2018-03-18 15:06:51 -07:00
import android.database.Cursor ;
2018-04-06 18:15:24 -07:00
import android.os.SystemClock ;
2018-01-24 19:17:44 -08:00
import android.support.annotation.NonNull ;
import android.util.Log ;
import net.sqlcipher.database.SQLiteDatabase ;
import net.sqlcipher.database.SQLiteDatabaseHook ;
import net.sqlcipher.database.SQLiteOpenHelper ;
2018-02-15 20:33:10 -08:00
import org.thoughtcrime.securesms.ApplicationContext ;
2018-01-24 19:17:44 -08:00
import org.thoughtcrime.securesms.crypto.DatabaseSecret ;
import org.thoughtcrime.securesms.crypto.MasterSecret ;
import org.thoughtcrime.securesms.database.AttachmentDatabase ;
import org.thoughtcrime.securesms.database.DraftDatabase ;
import org.thoughtcrime.securesms.database.GroupDatabase ;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase ;
import org.thoughtcrime.securesms.database.IdentityDatabase ;
import org.thoughtcrime.securesms.database.MmsDatabase ;
2018-02-15 20:33:10 -08:00
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase ;
2018-01-24 19:17:44 -08:00
import org.thoughtcrime.securesms.database.PushDatabase ;
import org.thoughtcrime.securesms.database.RecipientDatabase ;
2018-04-06 18:15:24 -07:00
import org.thoughtcrime.securesms.database.SearchDatabase ;
2018-02-18 16:43:18 -08:00
import org.thoughtcrime.securesms.database.SessionDatabase ;
2018-02-15 20:33:10 -08:00
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase ;
2018-01-24 19:17:44 -08:00
import org.thoughtcrime.securesms.database.SmsDatabase ;
import org.thoughtcrime.securesms.database.ThreadDatabase ;
2018-02-15 20:33:10 -08:00
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob ;
2018-01-24 19:17:44 -08:00
import org.thoughtcrime.securesms.service.KeyCachingService ;
import org.thoughtcrime.securesms.util.TextSecurePreferences ;
2018-03-18 15:06:51 -07:00
import java.io.File ;
2018-01-24 19:17:44 -08:00
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
2018-02-16 11:10:35 -08:00
@SuppressWarnings ( " unused " )
2018-01-24 19:17:44 -08:00
private static final String TAG = SQLCipherOpenHelper . class . getSimpleName ( ) ;
2018-03-18 15:06:51 -07:00
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2 ;
private static final int MIGRATE_PREKEYS_VERSION = 3 ;
private static final int MIGRATE_SESSIONS_VERSION = 4 ;
private static final int NO_MORE_IMAGE_THUMBNAILS_VERSION = 5 ;
2018-03-18 16:04:33 -07:00
private static final int ATTACHMENT_DIMENSIONS = 6 ;
2018-04-02 16:17:32 -07:00
private static final int QUOTED_REPLIES = 7 ;
2018-04-26 17:03:54 -07:00
private static final int SHARED_CONTACTS = 8 ;
2018-04-06 18:15:24 -07:00
private static final int FULL_TEXT_SEARCH = 9 ;
2018-02-16 11:10:35 -08:00
2018-04-06 18:15:24 -07:00
private static final int DATABASE_VERSION = 9 ;
2018-01-24 19:17:44 -08:00
private static final String DATABASE_NAME = " signal.db " ;
private final Context context ;
private final DatabaseSecret databaseSecret ;
public SQLCipherOpenHelper ( @NonNull Context context , @NonNull DatabaseSecret databaseSecret ) {
super ( context , DATABASE_NAME , null , DATABASE_VERSION , new SQLiteDatabaseHook ( ) {
@Override
public void preKey ( SQLiteDatabase db ) {
db . rawExecSQL ( " PRAGMA cipher_default_kdf_iter = 1; " ) ;
db . rawExecSQL ( " PRAGMA cipher_default_page_size = 4096; " ) ;
}
@Override
public void postKey ( SQLiteDatabase db ) {
db . rawExecSQL ( " PRAGMA kdf_iter = '1'; " ) ;
db . rawExecSQL ( " PRAGMA cipher_page_size = 4096; " ) ;
}
} ) ;
this . context = context . getApplicationContext ( ) ;
this . databaseSecret = databaseSecret ;
}
@Override
public void onCreate ( SQLiteDatabase db ) {
db . execSQL ( SmsDatabase . CREATE_TABLE ) ;
db . execSQL ( MmsDatabase . CREATE_TABLE ) ;
db . execSQL ( AttachmentDatabase . CREATE_TABLE ) ;
db . execSQL ( ThreadDatabase . CREATE_TABLE ) ;
db . execSQL ( IdentityDatabase . CREATE_TABLE ) ;
db . execSQL ( DraftDatabase . CREATE_TABLE ) ;
db . execSQL ( PushDatabase . CREATE_TABLE ) ;
db . execSQL ( GroupDatabase . CREATE_TABLE ) ;
db . execSQL ( RecipientDatabase . CREATE_TABLE ) ;
db . execSQL ( GroupReceiptDatabase . CREATE_TABLE ) ;
2018-02-15 20:33:10 -08:00
db . execSQL ( OneTimePreKeyDatabase . CREATE_TABLE ) ;
db . execSQL ( SignedPreKeyDatabase . CREATE_TABLE ) ;
2018-02-18 16:43:18 -08:00
db . execSQL ( SessionDatabase . CREATE_TABLE ) ;
2018-04-06 18:15:24 -07:00
for ( String sql : SearchDatabase . CREATE_TABLE ) {
db . execSQL ( sql ) ;
}
2018-01-24 19:17:44 -08:00
executeStatements ( db , SmsDatabase . CREATE_INDEXS ) ;
executeStatements ( db , MmsDatabase . CREATE_INDEXS ) ;
executeStatements ( db , AttachmentDatabase . CREATE_INDEXS ) ;
executeStatements ( db , ThreadDatabase . CREATE_INDEXS ) ;
executeStatements ( db , DraftDatabase . CREATE_INDEXS ) ;
executeStatements ( db , GroupDatabase . CREATE_INDEXS ) ;
executeStatements ( db , GroupReceiptDatabase . CREATE_INDEXES ) ;
if ( context . getDatabasePath ( ClassicOpenHelper . NAME ) . exists ( ) ) {
ClassicOpenHelper legacyHelper = new ClassicOpenHelper ( context ) ;
android . database . sqlite . SQLiteDatabase legacyDb = legacyHelper . getWritableDatabase ( ) ;
2018-02-01 16:01:24 -08:00
SQLCipherMigrationHelper . migratePlaintext ( context , legacyDb , db ) ;
2018-01-24 19:17:44 -08:00
MasterSecret masterSecret = KeyCachingService . getMasterSecret ( context ) ;
2018-02-01 16:01:24 -08:00
if ( masterSecret ! = null ) SQLCipherMigrationHelper . migrateCiphertext ( context , masterSecret , legacyDb , db , null ) ;
2018-01-24 19:17:44 -08:00
else TextSecurePreferences . setNeedsSqlCipherMigration ( context , true ) ;
2018-02-15 20:33:10 -08:00
if ( ! PreKeyMigrationHelper . migratePreKeys ( context , db ) ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new RefreshPreKeysJob ( context ) ) ;
}
2018-02-18 16:43:18 -08:00
SessionStoreMigrationHelper . migrateSessions ( context , db ) ;
2018-02-15 20:33:10 -08:00
PreKeyMigrationHelper . cleanUpPreKeys ( context ) ;
2018-01-24 19:17:44 -08:00
}
}
@Override
public void onUpgrade ( SQLiteDatabase db , int oldVersion , int newVersion ) {
2018-02-16 11:10:35 -08:00
Log . w ( TAG , " Upgrading database: " + oldVersion + " , " + newVersion ) ;
2018-01-24 19:17:44 -08:00
2018-02-15 20:33:10 -08:00
db . beginTransaction ( ) ;
try {
if ( oldVersion < RECIPIENT_CALL_RINGTONE_VERSION ) {
db . execSQL ( " ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL " ) ;
db . execSQL ( " ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase . VibrateState . DEFAULT . getId ( ) ) ;
}
if ( oldVersion < MIGRATE_PREKEYS_VERSION ) {
db . execSQL ( " CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0) " ) ;
db . execSQL ( " CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL) " ) ;
if ( ! PreKeyMigrationHelper . migratePreKeys ( context , db ) ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new RefreshPreKeysJob ( context ) ) ;
}
2018-02-18 16:43:18 -08:00
}
2018-02-15 20:33:10 -08:00
2018-02-18 16:43:18 -08:00
if ( oldVersion < MIGRATE_SESSIONS_VERSION ) {
db . execSQL ( " CREATE TABLE sessions (_id INTEGER PRIMARY KEY, address TEXT NOT NULL, device INTEGER NOT NULL, record BLOB NOT NULL, UNIQUE(address, device) ON CONFLICT REPLACE) " ) ;
SessionStoreMigrationHelper . migrateSessions ( context , db ) ;
2018-02-15 20:33:10 -08:00
}
2018-03-18 15:06:51 -07:00
if ( oldVersion < NO_MORE_IMAGE_THUMBNAILS_VERSION ) {
ContentValues update = new ContentValues ( ) ;
update . put ( " thumbnail " , ( String ) null ) ;
update . put ( " aspect_ratio " , ( String ) null ) ;
update . put ( " thumbnail_random " , ( String ) null ) ;
try ( Cursor cursor = db . query ( " part " , new String [ ] { " _id " , " ct " , " thumbnail " } , " thumbnail IS NOT NULL " , null , null , null , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
long id = cursor . getLong ( cursor . getColumnIndexOrThrow ( " _id " ) ) ;
String contentType = cursor . getString ( cursor . getColumnIndexOrThrow ( " ct " ) ) ;
if ( contentType ! = null & & ! contentType . startsWith ( " video " ) ) {
String thumbnailPath = cursor . getString ( cursor . getColumnIndexOrThrow ( " thumbnail " ) ) ;
File thumbnailFile = new File ( thumbnailPath ) ;
thumbnailFile . delete ( ) ;
db . update ( " part " , update , " _id = ? " , new String [ ] { String . valueOf ( id ) } ) ;
}
}
}
}
2018-03-18 16:04:33 -07:00
if ( oldVersion < ATTACHMENT_DIMENSIONS ) {
db . execSQL ( " ALTER TABLE part ADD COLUMN width INTEGER DEFAULT 0 " ) ;
db . execSQL ( " ALTER TABLE part ADD COLUMN height INTEGER DEFAULT 0 " ) ;
}
2018-04-02 16:17:32 -07:00
if ( oldVersion < QUOTED_REPLIES ) {
db . execSQL ( " ALTER TABLE mms ADD COLUMN quote_id INTEGER DEFAULT 0 " ) ;
db . execSQL ( " ALTER TABLE mms ADD COLUMN quote_author TEXT " ) ;
db . execSQL ( " ALTER TABLE mms ADD COLUMN quote_body TEXT " ) ;
db . execSQL ( " ALTER TABLE mms ADD COLUMN quote_attachment INTEGER DEFAULT -1 " ) ;
db . execSQL ( " ALTER TABLE part ADD COLUMN quote INTEGER DEFAULT 0 " ) ;
}
2018-04-26 17:03:54 -07:00
if ( oldVersion < SHARED_CONTACTS ) {
db . execSQL ( " ALTER TABLE mms ADD COLUMN shared_contacts TEXT " ) ;
}
2018-04-06 18:15:24 -07:00
if ( oldVersion < FULL_TEXT_SEARCH ) {
for ( String sql : SearchDatabase . CREATE_TABLE ) {
db . execSQL ( sql ) ;
}
Log . i ( TAG , " Beginning to build search index. " ) ;
long start = SystemClock . elapsedRealtime ( ) ;
db . execSQL ( " INSERT INTO " + SearchDatabase . SMS_FTS_TABLE_NAME + " (rowid, " + SearchDatabase . BODY + " ) " +
" SELECT " + SmsDatabase . ID + " , " + SmsDatabase . BODY + " FROM " + SmsDatabase . TABLE_NAME ) ;
long smsFinished = SystemClock . elapsedRealtime ( ) ;
Log . i ( TAG , " Indexing SMS completed in " + ( smsFinished - start ) + " ms " ) ;
db . execSQL ( " INSERT INTO " + SearchDatabase . MMS_FTS_TABLE_NAME + " (rowid, " + SearchDatabase . BODY + " ) " +
" SELECT " + MmsDatabase . ID + " , " + MmsDatabase . BODY + " FROM " + MmsDatabase . TABLE_NAME ) ;
long mmsFinished = SystemClock . elapsedRealtime ( ) ;
Log . i ( TAG , " Indexing MMS completed in " + ( mmsFinished - smsFinished ) + " ms " ) ;
Log . i ( TAG , " Indexing finished. Total time: " + ( mmsFinished - start ) + " ms " ) ;
}
2018-02-15 20:33:10 -08:00
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
if ( oldVersion < MIGRATE_PREKEYS_VERSION ) {
PreKeyMigrationHelper . cleanUpPreKeys ( context ) ;
2018-02-16 11:10:35 -08:00
}
2018-01-24 19:17:44 -08:00
}
public SQLiteDatabase getReadableDatabase ( ) {
return getReadableDatabase ( databaseSecret . asString ( ) ) ;
}
public SQLiteDatabase getWritableDatabase ( ) {
return getWritableDatabase ( databaseSecret . asString ( ) ) ;
}
2018-04-02 06:27:50 -07:00
public void markCurrent ( SQLiteDatabase db ) {
db . setVersion ( DATABASE_VERSION ) ;
}
2018-01-24 19:17:44 -08:00
private void executeStatements ( SQLiteDatabase db , String [ ] statements ) {
for ( String statement : statements )
db . execSQL ( statement ) ;
}
}