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-08-16 09:47:43 -07:00
import android.net.Uri ;
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 ;
2018-06-21 16:48:46 -07:00
import android.text.TextUtils ;
2018-08-16 09:47:43 -07:00
import org.thoughtcrime.securesms.database.Address ;
2018-08-01 11:09:24 -04:00
import org.thoughtcrime.securesms.logging.Log ;
2018-01-24 19:17:44 -08:00
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-08-16 09:47:43 -07:00
import org.thoughtcrime.securesms.notifications.NotificationChannels ;
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-06-21 16:48:46 -07:00
private static final int BAD_IMPORT_CLEANUP = 10 ;
2018-08-11 09:55:52 -04:00
private static final int QUOTE_MISSING = 11 ;
2018-08-16 09:47:43 -07:00
private static final int NOTIFICATION_CHANNELS = 12 ;
2018-02-16 11:10:35 -08:00
2018-08-16 09:47:43 -07:00
private static final int DATABASE_VERSION = 12 ;
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-08-02 09:25:33 -04:00
Log . i ( 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 ( ) ;
2018-07-02 18:10:11 -07:00
db . execSQL ( " INSERT INTO sms_fts (rowid, body) SELECT _id, body FROM sms " ) ;
2018-04-06 18:15:24 -07:00
long smsFinished = SystemClock . elapsedRealtime ( ) ;
Log . i ( TAG , " Indexing SMS completed in " + ( smsFinished - start ) + " ms " ) ;
2018-07-02 18:10:11 -07:00
db . execSQL ( " INSERT INTO mms_fts (rowid, body) SELECT _id, body FROM mms " ) ;
2018-04-06 18:15:24 -07:00
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-06-21 16:48:46 -07:00
if ( oldVersion < BAD_IMPORT_CLEANUP ) {
String trimmedCondition = " NOT IN (SELECT _id FROM mms) " ;
db . delete ( " group_receipts " , " mms_id " + trimmedCondition , null ) ;
String [ ] columns = new String [ ] { " _id " , " unique_id " , " _data " , " thumbnail " } ;
try ( Cursor cursor = db . query ( " part " , columns , " mid " + trimmedCondition , null , null , null , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
db . delete ( " part " , " _id = ? AND unique_id = ? " , new String [ ] { String . valueOf ( cursor . getLong ( 0 ) ) , String . valueOf ( cursor . getLong ( 1 ) ) } ) ;
String data = cursor . getString ( 2 ) ;
String thumbnail = cursor . getString ( 3 ) ;
if ( ! TextUtils . isEmpty ( data ) ) {
new File ( data ) . delete ( ) ;
}
if ( ! TextUtils . isEmpty ( thumbnail ) ) {
new File ( thumbnail ) . delete ( ) ;
}
}
}
}
2018-08-11 09:55:52 -04:00
if ( oldVersion < QUOTE_MISSING ) {
db . execSQL ( " ALTER TABLE mms ADD COLUMN quote_missing INTEGER DEFAULT 0 " ) ;
}
2018-08-16 09:47:43 -07:00
if ( oldVersion < NOTIFICATION_CHANNELS ) {
db . execSQL ( " ALTER TABLE recipient_preferences ADD COLUMN notification_channel TEXT DEFAULT NULL " ) ;
2018-08-22 13:19:59 -07:00
NotificationChannels . create ( context ) ;
2018-08-16 09:47:43 -07:00
try ( Cursor cursor = db . rawQuery ( " SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0 " , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
String addressString = cursor . getString ( cursor . getColumnIndexOrThrow ( " recipient_ids " ) ) ;
Address address = Address . fromExternal ( context , addressString ) ;
String systemName = cursor . getString ( cursor . getColumnIndexOrThrow ( " system_display_name " ) ) ;
String profileName = cursor . getString ( cursor . getColumnIndexOrThrow ( " signal_profile_name " ) ) ;
String messageSound = cursor . getString ( cursor . getColumnIndexOrThrow ( " notification " ) ) ;
Uri messageSoundUri = messageSound ! = null ? Uri . parse ( messageSound ) : null ;
int vibrateState = cursor . getInt ( cursor . getColumnIndexOrThrow ( " vibrate " ) ) ;
2018-08-22 13:19:59 -07:00
String displayName = NotificationChannels . getChannelDisplayNameFor ( context , systemName , profileName , address ) ;
2018-08-16 09:47:43 -07:00
boolean vibrateEnabled = vibrateState = = 0 ? TextSecurePreferences . isNotificationVibrateEnabled ( context ) : vibrateState = = 1 ;
2018-08-22 14:19:37 -07:00
if ( address . isGroup ( ) ) {
try ( Cursor groupCursor = db . rawQuery ( " SELECT title FROM groups WHERE group_id = ? " , new String [ ] { address . toGroupString ( ) } ) ) {
if ( groupCursor ! = null & & groupCursor . moveToFirst ( ) ) {
String title = groupCursor . getString ( groupCursor . getColumnIndexOrThrow ( " title " ) ) ;
if ( ! TextUtils . isEmpty ( title ) ) {
displayName = title ;
}
}
}
}
2018-08-16 09:47:43 -07:00
String channelId = NotificationChannels . createChannelFor ( context , address , displayName , messageSoundUri , vibrateEnabled ) ;
ContentValues values = new ContentValues ( 1 ) ;
values . put ( " notification_channel " , channelId ) ;
db . update ( " recipient_preferences " , values , " recipient_ids = ? " , new String [ ] { addressString } ) ;
}
}
}
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 ) ;
}
}