2015-06-09 16:37:20 +02:00
package org.thoughtcrime.securesms.database ;
import android.content.ContentValues ;
import android.content.Context ;
import android.database.Cursor ;
import android.net.Uri ;
2019-08-27 00:09:01 +02:00
import android.text.TextUtils ;
2019-06-05 21:47:14 +02:00
import androidx.annotation.NonNull ;
import androidx.annotation.Nullable ;
2015-06-09 16:37:20 +02:00
2017-08-07 06:43:11 +02:00
import com.annimon.stream.Stream ;
2019-09-26 16:12:51 +02:00
import com.google.android.gms.common.util.ArrayUtils ;
2017-08-07 06:43:11 +02:00
2018-01-25 04:17:44 +01:00
import net.sqlcipher.database.SQLiteDatabase ;
2015-06-30 18:16:05 +02:00
import org.thoughtcrime.securesms.color.MaterialColor ;
2019-09-26 16:12:51 +02:00
import org.thoughtcrime.securesms.contacts.sync.StorageSyncHelper ;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord ;
2018-01-25 04:17:44 +01:00
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper ;
2019-11-08 21:33:10 +01:00
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies ;
2018-05-22 11:13:10 +02:00
import org.thoughtcrime.securesms.logging.Log ;
2019-12-20 21:12:22 +01:00
import org.thoughtcrime.securesms.profiles.ProfileName ;
2017-08-01 17:56:00 +02:00
import org.thoughtcrime.securesms.recipients.Recipient ;
2019-08-07 20:22:51 +02:00
import org.thoughtcrime.securesms.recipients.RecipientId ;
2017-08-15 03:11:13 +02:00
import org.thoughtcrime.securesms.util.Base64 ;
2019-09-26 16:12:51 +02:00
import org.thoughtcrime.securesms.util.FeatureFlags ;
2019-11-08 21:33:10 +01:00
import org.thoughtcrime.securesms.util.GroupUtil ;
2019-09-26 16:12:51 +02:00
import org.thoughtcrime.securesms.util.IdentityUtil ;
2019-12-05 17:41:47 +01:00
import org.thoughtcrime.securesms.util.SqlUtil ;
2017-11-26 19:45:39 +01:00
import org.thoughtcrime.securesms.util.Util ;
2019-09-26 16:12:51 +02:00
import org.whispersystems.libsignal.IdentityKey ;
import org.whispersystems.libsignal.InvalidKeyException ;
2019-12-05 17:41:47 +01:00
import org.whispersystems.libsignal.util.Pair ;
2016-03-23 18:34:41 +01:00
import org.whispersystems.libsignal.util.guava.Optional ;
2019-11-08 21:33:10 +01:00
import org.whispersystems.signalservice.api.push.SignalServiceAddress ;
2019-09-07 05:40:06 +02:00
import org.whispersystems.signalservice.api.util.UuidUtil ;
2019-09-26 16:12:51 +02:00
import org.whispersystems.signalservice.api.storage.SignalContactRecord ;
2015-06-09 16:37:20 +02:00
2018-08-16 18:47:43 +02:00
import java.io.Closeable ;
2017-08-15 03:11:13 +02:00
import java.io.IOException ;
2019-09-26 16:12:51 +02:00
import java.util.ArrayList ;
2019-09-07 05:40:06 +02:00
import java.util.Collection ;
2017-11-26 19:45:39 +01:00
import java.util.HashMap ;
2017-08-07 23:24:53 +02:00
import java.util.HashSet ;
import java.util.LinkedList ;
2017-08-07 06:43:11 +02:00
import java.util.List ;
2017-11-26 19:45:39 +01:00
import java.util.Map ;
2017-08-07 23:24:53 +02:00
import java.util.Set ;
2019-09-07 05:40:06 +02:00
import java.util.UUID ;
2019-11-15 21:33:54 +01:00
import java.util.concurrent.TimeUnit ;
2017-08-07 06:43:11 +02:00
2017-08-22 03:37:39 +02:00
public class RecipientDatabase extends Database {
2015-06-09 16:37:20 +02:00
2017-08-22 03:37:39 +02:00
private static final String TAG = RecipientDatabase . class . getSimpleName ( ) ;
2015-06-09 16:37:20 +02:00
2019-08-07 20:22:51 +02:00
static final String TABLE_NAME = "recipient" ;
public static final String ID = "_id" ;
private static final String UUID = "uuid" ;
2019-10-29 01:16:11 +01:00
private static final String USERNAME = "username" ;
2019-08-27 00:09:01 +02:00
public static final String PHONE = "phone" ;
public static final String EMAIL = "email" ;
2019-08-07 20:22:51 +02:00
static final String GROUP_ID = "group_id" ;
private static final String BLOCKED = "blocked" ;
private static final String MESSAGE_RINGTONE = "message_ringtone" ;
private static final String MESSAGE_VIBRATE = "message_vibrate" ;
private static final String CALL_RINGTONE = "call_ringtone" ;
private static final String CALL_VIBRATE = "call_vibrate" ;
private static final String NOTIFICATION_CHANNEL = "notification_channel" ;
2018-05-22 11:13:10 +02:00
private static final String MUTE_UNTIL = "mute_until" ;
private static final String COLOR = "color" ;
private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder" ;
private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id" ;
2019-08-07 20:22:51 +02:00
private static final String MESSAGE_EXPIRATION_TIME = "message_expiration_time" ;
2019-08-27 00:09:01 +02:00
public static final String REGISTERED = "registered" ;
public static final String SYSTEM_DISPLAY_NAME = "system_display_name" ;
2019-08-07 20:22:51 +02:00
private static final String SYSTEM_PHOTO_URI = "system_photo_uri" ;
2019-08-27 00:09:01 +02:00
public static final String SYSTEM_PHONE_TYPE = "system_phone_type" ;
public static final String SYSTEM_PHONE_LABEL = "system_phone_label" ;
2018-05-22 11:13:10 +02:00
private static final String SYSTEM_CONTACT_URI = "system_contact_uri" ;
2019-09-26 16:12:51 +02:00
private static final String SYSTEM_INFO_PENDING = "system_info_pending" ;
2019-08-07 20:22:51 +02:00
private static final String PROFILE_KEY = "profile_key" ;
2018-05-22 11:13:10 +02:00
private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar" ;
2019-08-07 20:22:51 +02:00
private static final String PROFILE_SHARING = "profile_sharing" ;
2018-05-22 11:13:10 +02:00
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode" ;
2019-04-12 21:22:38 +02:00
private static final String FORCE_SMS_SELECTION = "force_sms_selection" ;
2019-09-07 05:40:06 +02:00
private static final String UUID_SUPPORTED = "uuid_supported" ;
2019-09-26 16:12:51 +02:00
private static final String STORAGE_SERVICE_KEY = "storage_service_key" ;
private static final String DIRTY = "dirty" ;
2019-12-20 21:12:22 +01:00
private static final String PROFILE_GIVEN_NAME = "signal_profile_name" ;
private static final String PROFILE_FAMILY_NAME = "profile_family_name" ;
private static final String PROFILE_JOINED_NAME = "profile_joined_name" ;
2015-06-09 16:37:20 +02:00
2019-12-20 21:12:22 +01:00
public static final String SEARCH_PROFILE_NAME = "search_signal_profile" ;
2019-08-27 00:09:01 +02:00
private static final String SORT_NAME = "sort_name" ;
2019-09-26 16:12:51 +02:00
private static final String IDENTITY_STATUS = "identity_status" ;
private static final String IDENTITY_KEY = "identity_key" ;
2019-08-27 00:09:01 +02:00
2017-08-07 06:43:11 +02:00
private static final String [ ] RECIPIENT_PROJECTION = new String [ ] {
2019-10-29 01:16:11 +01:00
UUID , USERNAME , PHONE , EMAIL , GROUP_ID ,
2019-08-07 20:22:51 +02:00
BLOCKED , MESSAGE_RINGTONE , CALL_RINGTONE , MESSAGE_VIBRATE , CALL_VIBRATE , MUTE_UNTIL , COLOR , SEEN_INVITE_REMINDER , DEFAULT_SUBSCRIPTION_ID , MESSAGE_EXPIRATION_TIME , REGISTERED ,
2019-08-27 00:09:01 +02:00
PROFILE_KEY , SYSTEM_DISPLAY_NAME , SYSTEM_PHOTO_URI , SYSTEM_PHONE_LABEL , SYSTEM_PHONE_TYPE , SYSTEM_CONTACT_URI ,
2019-12-20 21:12:22 +01:00
PROFILE_GIVEN_NAME , PROFILE_FAMILY_NAME , SIGNAL_PROFILE_AVATAR , PROFILE_SHARING , NOTIFICATION_CHANNEL ,
2019-04-12 21:22:38 +02:00
UNIDENTIFIED_ACCESS_MODE ,
2019-09-26 16:12:51 +02:00
FORCE_SMS_SELECTION , UUID_SUPPORTED , STORAGE_SERVICE_KEY , DIRTY
} ;
private static final String [ ] RECIPIENT_FULL_PROJECTION = ArrayUtils . concat (
new String [ ] { TABLE_NAME + "." + ID } ,
RECIPIENT_PROJECTION ,
new String [ ] {
IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . VERIFIED + " AS " + IDENTITY_STATUS ,
IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . IDENTITY_KEY + " AS " + IDENTITY_KEY
} ) ;
public static final String [ ] CREATE_INDEXS = new String [ ] {
"CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");" ,
2017-08-07 06:43:11 +02:00
} ;
2019-10-29 01:16:11 +01:00
private static final String [ ] ID_PROJECTION = new String [ ] { ID } ;
2019-12-20 21:12:22 +01:00
private static final String [ ] SEARCH_PROJECTION = new String [ ] { ID , SYSTEM_DISPLAY_NAME , PHONE , EMAIL , SYSTEM_PHONE_LABEL , SYSTEM_PHONE_TYPE , REGISTERED , "COALESCE(" + PROFILE_JOINED_NAME + ", " + PROFILE_GIVEN_NAME + ") AS " + SEARCH_PROFILE_NAME , "COALESCE(" + SYSTEM_DISPLAY_NAME + ", " + PROFILE_JOINED_NAME + ", " + PROFILE_GIVEN_NAME + ", " + USERNAME + ") AS " + SORT_NAME } ;
public static final String [ ] SEARCH_PROJECTION_NAMES = new String [ ] { ID , SYSTEM_DISPLAY_NAME , PHONE , EMAIL , SYSTEM_PHONE_LABEL , SYSTEM_PHONE_TYPE , REGISTERED , SEARCH_PROFILE_NAME , SORT_NAME } ;
2019-09-07 05:40:06 +02:00
static final List < String > TYPED_RECIPIENT_PROJECTION = Stream . of ( RECIPIENT_PROJECTION )
. map ( columnName - > TABLE_NAME + "." + columnName )
. toList ( ) ;
2017-08-07 06:43:11 +02:00
2015-06-09 16:37:20 +02:00
public enum VibrateState {
DEFAULT ( 0 ) , ENABLED ( 1 ) , DISABLED ( 2 ) ;
private final int id ;
VibrateState ( int id ) {
this . id = id ;
}
public int getId ( ) {
return id ;
}
public static VibrateState fromId ( int id ) {
return values ( ) [ id ] ;
}
}
2017-08-22 19:44:04 +02:00
public enum RegisteredState {
UNKNOWN ( 0 ) , REGISTERED ( 1 ) , NOT_REGISTERED ( 2 ) ;
private final int id ;
RegisteredState ( int id ) {
this . id = id ;
}
public int getId ( ) {
return id ;
}
public static RegisteredState fromId ( int id ) {
return values ( ) [ id ] ;
}
}
2018-05-22 11:13:10 +02:00
public enum UnidentifiedAccessMode {
UNKNOWN ( 0 ) , DISABLED ( 1 ) , ENABLED ( 2 ) , UNRESTRICTED ( 3 ) ;
private final int mode ;
UnidentifiedAccessMode ( int mode ) {
this . mode = mode ;
}
public int getMode ( ) {
return mode ;
}
public static UnidentifiedAccessMode fromMode ( int mode ) {
return values ( ) [ mode ] ;
}
}
2019-11-12 15:18:57 +01:00
public enum InsightsBannerTier {
NO_TIER ( 0 ) , TIER_ONE ( 1 ) , TIER_TWO ( 2 ) ;
private final int id ;
InsightsBannerTier ( int id ) {
this . id = id ;
}
public int getId ( ) {
return id ;
}
public boolean seen ( InsightsBannerTier tier ) {
return tier . getId ( ) < = id ;
}
public static InsightsBannerTier fromId ( int id ) {
return values ( ) [ id ] ;
}
}
2019-09-26 16:12:51 +02:00
enum DirtyState {
CLEAN ( 0 ) , UPDATE ( 1 ) , INSERT ( 2 ) , DELETE ( 3 ) ;
private final int id ;
DirtyState ( int id ) {
this . id = id ;
}
int getId ( ) {
return id ;
}
}
2015-06-09 16:37:20 +02:00
public static final String CREATE_TABLE =
2019-08-07 20:22:51 +02:00
"CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
UUID + " TEXT UNIQUE DEFAULT NULL, " +
2019-10-29 01:16:11 +01:00
USERNAME + " TEXT UNIQUE DEFAULT NULL, " +
2019-08-07 20:22:51 +02:00
PHONE + " TEXT UNIQUE DEFAULT NULL, " +
EMAIL + " TEXT UNIQUE DEFAULT NULL, " +
GROUP_ID + " TEXT UNIQUE DEFAULT NULL, " +
BLOCKED + " INTEGER DEFAULT 0," +
MESSAGE_RINGTONE + " TEXT DEFAULT NULL, " +
MESSAGE_VIBRATE + " INTEGER DEFAULT " + VibrateState . DEFAULT . getId ( ) + ", " +
CALL_RINGTONE + " TEXT DEFAULT NULL, " +
CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState . DEFAULT . getId ( ) + ", " +
NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " +
MUTE_UNTIL + " INTEGER DEFAULT 0, " +
COLOR + " TEXT DEFAULT NULL, " +
2019-11-12 15:18:57 +01:00
SEEN_INVITE_REMINDER + " INTEGER DEFAULT " + InsightsBannerTier . NO_TIER . getId ( ) + ", " +
2019-08-07 20:22:51 +02:00
DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
MESSAGE_EXPIRATION_TIME + " INTEGER DEFAULT 0, " +
REGISTERED + " INTEGER DEFAULT " + RegisteredState . UNKNOWN . getId ( ) + ", " +
SYSTEM_DISPLAY_NAME + " TEXT DEFAULT NULL, " +
SYSTEM_PHOTO_URI + " TEXT DEFAULT NULL, " +
SYSTEM_PHONE_LABEL + " TEXT DEFAULT NULL, " +
2019-08-27 00:09:01 +02:00
SYSTEM_PHONE_TYPE + " INTEGER DEFAULT -1, " +
2019-08-07 20:22:51 +02:00
SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " +
2019-09-26 16:12:51 +02:00
SYSTEM_INFO_PENDING + " INTEGER DEFAULT 0, " +
2019-08-07 20:22:51 +02:00
PROFILE_KEY + " TEXT DEFAULT NULL, " +
2019-12-20 21:12:22 +01:00
PROFILE_GIVEN_NAME + " TEXT DEFAULT NULL, " +
PROFILE_FAMILY_NAME + " TEXT DEFAULT NULL, " +
PROFILE_JOINED_NAME + " TEXT DEFAULT NULL, " +
2019-08-07 20:22:51 +02:00
SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
PROFILE_SHARING + " INTEGER DEFAULT 0, " +
UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " +
2019-09-07 05:40:06 +02:00
FORCE_SMS_SELECTION + " INTEGER DEFAULT 0, " +
2019-09-26 16:12:51 +02:00
UUID_SUPPORTED + " INTEGER DEFAULT 0, " +
STORAGE_SERVICE_KEY + " TEXT UNIQUE DEFAULT NULL, " +
DIRTY + " INTEGER DEFAULT 0);" ;
2015-06-09 16:37:20 +02:00
2019-11-12 15:18:57 +01:00
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
" FROM " + TABLE_NAME +
" INNER JOIN " + ThreadDatabase . TABLE_NAME +
" ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . RECIPIENT_ID +
" WHERE " +
TABLE_NAME + "." + GROUP_ID + " IS NULL AND " +
TABLE_NAME + "." + REGISTERED + " = " + RegisteredState . NOT_REGISTERED . id + " AND " +
TABLE_NAME + "." + SEEN_INVITE_REMINDER + " < " + InsightsBannerTier . TIER_TWO . id + " AND " +
2019-11-15 21:33:54 +01:00
ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . HAS_SENT + " AND " +
ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . DATE + " > ?" +
" ORDER BY " + ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . DATE + " DESC LIMIT 50" ;
2019-11-12 15:18:57 +01:00
2018-01-25 04:17:44 +01:00
public RecipientDatabase ( Context context , SQLCipherOpenHelper databaseHelper ) {
2015-06-09 16:37:20 +02:00
super ( context , databaseHelper ) ;
}
2019-09-07 05:40:06 +02:00
public @NonNull boolean containsPhoneOrUuid ( @NonNull String id ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
String query = UUID + " = ? OR " + PHONE + " = ?" ;
String [ ] args = new String [ ] { id , id } ;
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { ID } , query , args , null , null , null ) ) {
return cursor ! = null & & cursor . moveToFirst ( ) ;
}
}
2019-10-07 22:36:05 +02:00
public @NonNull Optional < RecipientId > getByE164 ( @NonNull String e164 ) {
return getByColumn ( PHONE , e164 ) ;
}
2019-10-04 18:07:46 +02:00
2019-10-07 22:36:05 +02:00
public @NonNull Optional < RecipientId > getByEmail ( @NonNull String email ) {
return getByColumn ( EMAIL , email ) ;
}
2019-10-04 18:07:46 +02:00
2019-10-07 22:36:05 +02:00
public @NonNull Optional < RecipientId > getByGroupId ( @NonNull String groupId ) {
return getByColumn ( GROUP_ID , groupId ) ;
2019-09-07 05:40:06 +02:00
}
public @NonNull Optional < RecipientId > getByUuid ( @NonNull UUID uuid ) {
return getByColumn ( UUID , uuid . toString ( ) ) ;
}
2019-10-29 01:16:11 +01:00
public @NonNull Optional < RecipientId > getByUsername ( @NonNull String username ) {
return getByColumn ( USERNAME , username ) ;
}
2019-09-07 05:40:06 +02:00
public @NonNull RecipientId getOrInsertFromUuid ( @NonNull UUID uuid ) {
return getOrInsertByColumn ( UUID , uuid . toString ( ) ) ;
2019-10-07 22:36:05 +02:00
}
2019-10-04 18:07:46 +02:00
2019-10-07 22:36:05 +02:00
public @NonNull RecipientId getOrInsertFromE164 ( @NonNull String e164 ) {
return getOrInsertByColumn ( PHONE , e164 ) ;
2019-08-07 20:22:51 +02:00
}
public RecipientId getOrInsertFromEmail ( @NonNull String email ) {
2019-10-07 22:36:05 +02:00
return getOrInsertByColumn ( EMAIL , email ) ;
2019-08-07 20:22:51 +02:00
}
public RecipientId getOrInsertFromGroupId ( @NonNull String groupId ) {
2019-10-07 22:36:05 +02:00
return getOrInsertByColumn ( GROUP_ID , groupId ) ;
2019-08-07 20:22:51 +02:00
}
2015-06-09 16:37:20 +02:00
public Cursor getBlocked ( ) {
SQLiteDatabase database = databaseHelper . getReadableDatabase ( ) ;
2019-08-07 20:22:51 +02:00
return database . query ( TABLE_NAME , ID_PROJECTION , BLOCKED + " = 1" ,
2017-11-26 19:45:39 +01:00
null , null , null , null , null ) ;
2015-06-09 16:37:20 +02:00
}
2018-08-16 18:47:43 +02:00
public RecipientReader readerForBlocked ( Cursor cursor ) {
2019-09-26 16:12:51 +02:00
return new RecipientReader ( cursor ) ;
2018-08-16 18:47:43 +02:00
}
public RecipientReader getRecipientsWithNotificationChannels ( ) {
SQLiteDatabase database = databaseHelper . getReadableDatabase ( ) ;
2019-08-07 20:22:51 +02:00
Cursor cursor = database . query ( TABLE_NAME , ID_PROJECTION , NOTIFICATION_CHANNEL + " NOT NULL" ,
2018-08-16 18:47:43 +02:00
null , null , null , null , null ) ;
2019-09-26 16:12:51 +02:00
return new RecipientReader ( cursor ) ;
2016-08-27 01:53:23 +02:00
}
2019-08-07 20:22:51 +02:00
public @NonNull RecipientSettings getRecipientSettings ( @NonNull RecipientId id ) {
2015-06-09 16:37:20 +02:00
SQLiteDatabase database = databaseHelper . getReadableDatabase ( ) ;
2019-09-26 16:12:51 +02:00
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase . TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . RECIPIENT_ID ;
String query = TABLE_NAME + "." + ID + " = ?" ;
2019-08-07 20:22:51 +02:00
String [ ] args = new String [ ] { id . serialize ( ) } ;
2015-06-09 16:37:20 +02:00
2019-09-26 16:12:51 +02:00
try ( Cursor cursor = database . query ( table , RECIPIENT_FULL_PROJECTION , query , args , null , null , null ) ) {
2015-06-09 16:37:20 +02:00
if ( cursor ! = null & & cursor . moveToNext ( ) ) {
2017-08-22 19:44:04 +02:00
return getRecipientSettings ( cursor ) ;
2019-08-07 20:22:51 +02:00
} else {
2019-10-02 18:06:13 +02:00
throw new MissingRecipientError ( id ) ;
2015-06-09 16:37:20 +02:00
}
}
}
2019-09-26 16:12:51 +02:00
public @NonNull List < RecipientSettings > getPendingRecipientSyncUpdates ( ) {
return getRecipientSettings ( DIRTY + " = ?" , new String [ ] { String . valueOf ( DirtyState . UPDATE . getId ( ) ) } ) ;
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncInsertions ( ) {
return getRecipientSettings ( DIRTY + " = ?" , new String [ ] { String . valueOf ( DirtyState . INSERT . getId ( ) ) } ) ;
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncDeletions ( ) {
return getRecipientSettings ( DIRTY + " = ?" , new String [ ] { String . valueOf ( DirtyState . DELETE . getId ( ) ) } ) ;
}
public @Nullable RecipientSettings getByStorageSyncKey ( @NonNull byte [ ] key ) {
List < RecipientSettings > result = getRecipientSettings ( STORAGE_SERVICE_KEY + " = ?" , new String [ ] { Base64 . encodeBytes ( key ) } ) ;
if ( result . size ( ) > 0 ) {
return result . get ( 0 ) ;
}
return null ;
}
public void applyStorageSyncKeyUpdates ( @NonNull Map < RecipientId , byte [ ] > keys ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
String query = ID + " = ?" ;
for ( Map . Entry < RecipientId , byte [ ] > entry : keys . entrySet ( ) ) {
ContentValues values = new ContentValues ( ) ;
values . put ( STORAGE_SERVICE_KEY , Base64 . encodeBytes ( entry . getValue ( ) ) ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
db . update ( TABLE_NAME , values , query , new String [ ] { entry . getKey ( ) . serialize ( ) } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
public void applyStorageSyncUpdates ( @NonNull Collection < SignalContactRecord > inserts ,
@NonNull Collection < StorageSyncHelper . ContactUpdate > updates )
{
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
IdentityDatabase identityDatabase = DatabaseFactory . getIdentityDatabase ( context ) ;
db . beginTransaction ( ) ;
try {
for ( SignalContactRecord insert : inserts ) {
ContentValues values = getValuesForStorageContact ( insert ) ;
long id = db . insertOrThrow ( TABLE_NAME , null , values ) ;
RecipientId recipientId = RecipientId . from ( id ) ;
if ( insert . getIdentityKey ( ) . isPresent ( ) ) {
try {
IdentityKey identityKey = new IdentityKey ( insert . getIdentityKey ( ) . get ( ) , 0 ) ;
DatabaseFactory . getIdentityDatabase ( context ) . updateIdentityAfterSync ( recipientId , identityKey , StorageSyncHelper . remoteToLocalIdentityStatus ( insert . getIdentityState ( ) ) ) ;
IdentityUtil . markIdentityVerified ( context , Recipient . resolved ( recipientId ) , true , true ) ;
} catch ( InvalidKeyException e ) {
Log . w ( TAG , "Failed to process identity key during insert! Skipping." , e ) ;
}
}
}
for ( StorageSyncHelper . ContactUpdate update : updates ) {
ContentValues values = getValuesForStorageContact ( update . getNewContact ( ) ) ;
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_KEY + " = ?" , new String [ ] { Base64 . encodeBytes ( update . getOldContact ( ) . getKey ( ) ) } ) ;
if ( updateCount < 1 ) {
throw new AssertionError ( "Had an update, but it didn't match any rows!" ) ;
}
RecipientId recipientId = getByStorageKeyOrThrow ( update . getNewContact ( ) . getKey ( ) ) ;
try {
Optional < IdentityRecord > oldIdentityRecord = identityDatabase . getIdentity ( recipientId ) ;
IdentityKey identityKey = update . getNewContact ( ) . getIdentityKey ( ) . isPresent ( ) ? new IdentityKey ( update . getNewContact ( ) . getIdentityKey ( ) . get ( ) , 0 ) : null ;
DatabaseFactory . getIdentityDatabase ( context ) . updateIdentityAfterSync ( recipientId , identityKey , StorageSyncHelper . remoteToLocalIdentityStatus ( update . getNewContact ( ) . getIdentityState ( ) ) ) ;
Optional < IdentityRecord > newIdentityRecord = identityDatabase . getIdentity ( recipientId ) ;
if ( ( newIdentityRecord . isPresent ( ) & & newIdentityRecord . get ( ) . getVerifiedStatus ( ) = = IdentityDatabase . VerifiedStatus . VERIFIED ) & &
( ! oldIdentityRecord . isPresent ( ) | | oldIdentityRecord . get ( ) . getVerifiedStatus ( ) ! = IdentityDatabase . VerifiedStatus . VERIFIED ) )
{
IdentityUtil . markIdentityVerified ( context , Recipient . resolved ( recipientId ) , true , true ) ;
} else if ( ( newIdentityRecord . isPresent ( ) & & newIdentityRecord . get ( ) . getVerifiedStatus ( ) ! = IdentityDatabase . VerifiedStatus . VERIFIED ) & &
( oldIdentityRecord . isPresent ( ) & & oldIdentityRecord . get ( ) . getVerifiedStatus ( ) = = IdentityDatabase . VerifiedStatus . VERIFIED ) )
{
IdentityUtil . markIdentityVerified ( context , Recipient . resolved ( recipientId ) , false , true ) ;
}
} catch ( InvalidKeyException e ) {
Log . w ( TAG , "Failed to process identity key during update! Skipping." , e ) ;
}
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
2020-01-18 00:32:26 +01:00
public void updatePhoneNumbers ( @NonNull Map < String , String > mapping ) {
if ( mapping . isEmpty ( ) ) return ;
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
String query = PHONE + " = ?" ;
for ( Map . Entry < String , String > entry : mapping . entrySet ( ) ) {
ContentValues values = new ContentValues ( ) ;
values . put ( PHONE , entry . getValue ( ) ) ;
db . updateWithOnConflict ( TABLE_NAME , values , query , new String [ ] { entry . getKey ( ) } , SQLiteDatabase . CONFLICT_IGNORE ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
2019-09-26 16:12:51 +02:00
private @NonNull RecipientId getByStorageKeyOrThrow ( byte [ ] storageKey ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
String query = STORAGE_SERVICE_KEY + " = ?" ;
String [ ] args = new String [ ] { Base64 . encodeBytes ( storageKey ) } ;
try ( Cursor cursor = db . query ( TABLE_NAME , ID_PROJECTION , query , args , null , null , null ) ) {
if ( cursor ! = null & & cursor . moveToFirst ( ) ) {
long id = cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ;
return RecipientId . from ( id ) ;
} else {
throw new AssertionError ( "No recipient with that storage key!" ) ;
}
}
}
private static @NonNull ContentValues getValuesForStorageContact ( @NonNull SignalContactRecord contact ) {
ContentValues values = new ContentValues ( ) ;
if ( contact . getAddress ( ) . getUuid ( ) . isPresent ( ) ) {
values . put ( UUID , contact . getAddress ( ) . getUuid ( ) . get ( ) . toString ( ) ) ;
}
2019-12-20 21:12:22 +01:00
ProfileName profileName = ProfileName . fromSerialized ( contact . getProfileName ( ) . orNull ( ) ) ;
2019-09-26 16:12:51 +02:00
values . put ( PHONE , contact . getAddress ( ) . getNumber ( ) . orNull ( ) ) ;
2019-12-20 21:12:22 +01:00
values . put ( PROFILE_GIVEN_NAME , profileName . getGivenName ( ) ) ;
values . put ( PROFILE_FAMILY_NAME , profileName . getFamilyName ( ) ) ;
values . put ( PROFILE_JOINED_NAME , profileName . toString ( ) ) ;
2019-09-26 16:12:51 +02:00
values . put ( PROFILE_KEY , contact . getProfileKey ( ) . orNull ( ) ) ;
// TODO [greyson] Username
values . put ( PROFILE_SHARING , contact . isProfileSharingEnabled ( ) ? "1" : "0" ) ;
values . put ( BLOCKED , contact . isBlocked ( ) ? "1" : "0" ) ;
values . put ( STORAGE_SERVICE_KEY , Base64 . encodeBytes ( contact . getKey ( ) ) ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
return values ;
}
private List < RecipientSettings > getRecipientSettings ( @Nullable String query , @Nullable String [ ] args ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase . TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . RECIPIENT_ID ;
List < RecipientSettings > out = new ArrayList < > ( ) ;
try ( Cursor cursor = db . query ( table , RECIPIENT_FULL_PROJECTION , query , args , null , null , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
out . add ( getRecipientSettings ( cursor ) ) ;
}
}
return out ;
}
/ * *
* @return All storage keys , excluding the ones that need to be deleted .
* /
public List < byte [ ] > getAllStorageSyncKeys ( ) {
return new ArrayList < > ( getAllStorageSyncKeysMap ( ) . values ( ) ) ;
}
/ * *
* @return All storage keys , excluding the ones that need to be deleted .
* /
public Map < RecipientId , byte [ ] > getAllStorageSyncKeysMap ( ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
String query = STORAGE_SERVICE_KEY + " NOT NULL AND " + DIRTY + " != ?" ;
String [ ] args = new String [ ] { String . valueOf ( DirtyState . DELETE ) } ;
Map < RecipientId , byte [ ] > out = new HashMap < > ( ) ;
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { ID , STORAGE_SERVICE_KEY } , query , args , null , null , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
RecipientId id = RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ;
String encodedKey = cursor . getString ( cursor . getColumnIndexOrThrow ( STORAGE_SERVICE_KEY ) ) ;
try {
out . put ( id , Base64 . decode ( encodedKey ) ) ;
} catch ( IOException e ) {
throw new AssertionError ( e ) ;
}
}
}
return out ;
}
2019-08-07 20:22:51 +02:00
@NonNull RecipientSettings getRecipientSettings ( @NonNull Cursor cursor ) {
long id = cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ;
2019-09-07 05:40:06 +02:00
UUID uuid = UuidUtil . parseOrNull ( cursor . getString ( cursor . getColumnIndexOrThrow ( UUID ) ) ) ;
2019-10-29 01:16:11 +01:00
String username = cursor . getString ( cursor . getColumnIndexOrThrow ( USERNAME ) ) ;
2019-09-07 05:40:06 +02:00
String e164 = cursor . getString ( cursor . getColumnIndexOrThrow ( PHONE ) ) ;
String email = cursor . getString ( cursor . getColumnIndexOrThrow ( EMAIL ) ) ;
String groupId = cursor . getString ( cursor . getColumnIndexOrThrow ( GROUP_ID ) ) ;
2019-08-07 20:22:51 +02:00
boolean blocked = cursor . getInt ( cursor . getColumnIndexOrThrow ( BLOCKED ) ) = = 1 ;
String messageRingtone = cursor . getString ( cursor . getColumnIndexOrThrow ( MESSAGE_RINGTONE ) ) ;
2018-05-22 11:13:10 +02:00
String callRingtone = cursor . getString ( cursor . getColumnIndexOrThrow ( CALL_RINGTONE ) ) ;
2019-08-07 20:22:51 +02:00
int messageVibrateState = cursor . getInt ( cursor . getColumnIndexOrThrow ( MESSAGE_VIBRATE ) ) ;
2018-05-22 11:13:10 +02:00
int callVibrateState = cursor . getInt ( cursor . getColumnIndexOrThrow ( CALL_VIBRATE ) ) ;
long muteUntil = cursor . getLong ( cursor . getColumnIndexOrThrow ( MUTE_UNTIL ) ) ;
String serializedColor = cursor . getString ( cursor . getColumnIndexOrThrow ( COLOR ) ) ;
2019-11-12 15:18:57 +01:00
int insightsBannerTier = cursor . getInt ( cursor . getColumnIndexOrThrow ( SEEN_INVITE_REMINDER ) ) ;
2018-05-22 11:13:10 +02:00
int defaultSubscriptionId = cursor . getInt ( cursor . getColumnIndexOrThrow ( DEFAULT_SUBSCRIPTION_ID ) ) ;
2019-08-07 20:22:51 +02:00
int expireMessages = cursor . getInt ( cursor . getColumnIndexOrThrow ( MESSAGE_EXPIRATION_TIME ) ) ;
2018-05-22 11:13:10 +02:00
int registeredState = cursor . getInt ( cursor . getColumnIndexOrThrow ( REGISTERED ) ) ;
String profileKeyString = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_KEY ) ) ;
String systemDisplayName = cursor . getString ( cursor . getColumnIndexOrThrow ( SYSTEM_DISPLAY_NAME ) ) ;
String systemContactPhoto = cursor . getString ( cursor . getColumnIndexOrThrow ( SYSTEM_PHOTO_URI ) ) ;
String systemPhoneLabel = cursor . getString ( cursor . getColumnIndexOrThrow ( SYSTEM_PHONE_LABEL ) ) ;
String systemContactUri = cursor . getString ( cursor . getColumnIndexOrThrow ( SYSTEM_CONTACT_URI ) ) ;
2019-12-20 21:12:22 +01:00
String profileGivenName = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_GIVEN_NAME ) ) ;
String profileFamilyName = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_FAMILY_NAME ) ) ;
2018-05-22 11:13:10 +02:00
String signalProfileAvatar = cursor . getString ( cursor . getColumnIndexOrThrow ( SIGNAL_PROFILE_AVATAR ) ) ;
boolean profileSharing = cursor . getInt ( cursor . getColumnIndexOrThrow ( PROFILE_SHARING ) ) = = 1 ;
String notificationChannel = cursor . getString ( cursor . getColumnIndexOrThrow ( NOTIFICATION_CHANNEL ) ) ;
int unidentifiedAccessMode = cursor . getInt ( cursor . getColumnIndexOrThrow ( UNIDENTIFIED_ACCESS_MODE ) ) ;
2019-04-12 21:22:38 +02:00
boolean forceSmsSelection = cursor . getInt ( cursor . getColumnIndexOrThrow ( FORCE_SMS_SELECTION ) ) = = 1 ;
2019-09-07 05:40:06 +02:00
boolean uuidSupported = cursor . getInt ( cursor . getColumnIndexOrThrow ( UUID_SUPPORTED ) ) = = 1 ;
2019-09-26 16:12:51 +02:00
String storageKeyRaw = cursor . getString ( cursor . getColumnIndexOrThrow ( STORAGE_SERVICE_KEY ) ) ;
String identityKeyRaw = cursor . getString ( cursor . getColumnIndexOrThrow ( IDENTITY_KEY ) ) ;
int identityStatusRaw = cursor . getInt ( cursor . getColumnIndexOrThrow ( IDENTITY_STATUS ) ) ;
2017-08-07 06:43:11 +02:00
MaterialColor color ;
2018-05-22 11:13:10 +02:00
byte [ ] profileKey = null ;
2017-08-07 06:43:11 +02:00
try {
color = serializedColor = = null ? null : MaterialColor . fromSerialized ( serializedColor ) ;
} catch ( MaterialColor . UnknownColorException e ) {
Log . w ( TAG , e ) ;
color = null ;
}
2017-08-15 03:11:13 +02:00
if ( profileKeyString ! = null ) {
try {
profileKey = Base64 . decode ( profileKeyString ) ;
} catch ( IOException e ) {
Log . w ( TAG , e ) ;
profileKey = null ;
}
}
2019-09-26 16:12:51 +02:00
byte [ ] storageKey = null ;
try {
storageKey = storageKeyRaw ! = null ? Base64 . decode ( storageKeyRaw ) : null ;
} catch ( IOException e ) {
throw new AssertionError ( e ) ;
}
byte [ ] identityKey = null ;
try {
identityKey = identityKeyRaw ! = null ? Base64 . decode ( identityKeyRaw ) : null ;
} catch ( IOException e ) {
throw new AssertionError ( e ) ;
}
IdentityDatabase . VerifiedStatus identityStatus = IdentityDatabase . VerifiedStatus . forState ( identityStatusRaw ) ;
2019-10-29 01:16:11 +01:00
return new RecipientSettings ( RecipientId . from ( id ) , uuid , username , e164 , email , groupId , blocked , muteUntil ,
2019-08-07 20:22:51 +02:00
VibrateState . fromId ( messageVibrateState ) ,
VibrateState . fromId ( callVibrateState ) ,
Util . uri ( messageRingtone ) , Util . uri ( callRingtone ) ,
2019-11-12 15:18:57 +01:00
color , defaultSubscriptionId , expireMessages ,
2019-08-07 20:22:51 +02:00
RegisteredState . fromId ( registeredState ) ,
profileKey , systemDisplayName , systemContactPhoto ,
systemPhoneLabel , systemContactUri ,
2019-12-20 21:12:22 +01:00
ProfileName . fromParts ( profileGivenName , profileFamilyName ) , signalProfileAvatar , profileSharing ,
2019-08-07 20:22:51 +02:00
notificationChannel , UnidentifiedAccessMode . fromMode ( unidentifiedAccessMode ) ,
2019-09-26 16:12:51 +02:00
forceSmsSelection , uuidSupported , InsightsBannerTier . fromId ( insightsBannerTier ) ,
storageKey , identityKey , identityStatus ) ;
2017-08-07 06:43:11 +02:00
}
2019-09-26 16:12:51 +02:00
public BulkOperationsHandle beginBulkSystemContactUpdate ( ) {
2017-08-16 04:23:42 +02:00
SQLiteDatabase database = databaseHelper . getWritableDatabase ( ) ;
database . beginTransaction ( ) ;
ContentValues contentValues = new ContentValues ( 1 ) ;
2019-09-26 16:12:51 +02:00
contentValues . put ( SYSTEM_INFO_PENDING , 1 ) ;
2017-08-16 04:23:42 +02:00
2019-09-26 16:12:51 +02:00
database . update ( TABLE_NAME , contentValues , SYSTEM_CONTACT_URI + " NOT NULL" , null ) ;
2017-08-16 04:23:42 +02:00
return new BulkOperationsHandle ( database ) ;
}
2019-08-07 20:22:51 +02:00
public void setColor ( @NonNull RecipientId id , @NonNull MaterialColor color ) {
2015-06-24 00:10:50 +02:00
ContentValues values = new ContentValues ( ) ;
2015-06-30 18:16:05 +02:00
values . put ( COLOR , color . serialize ( ) ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2015-06-24 00:10:50 +02:00
}
2019-08-07 20:22:51 +02:00
public void setDefaultSubscriptionId ( @NonNull RecipientId id , int defaultSubscriptionId ) {
2016-02-06 01:10:33 +01:00
ContentValues values = new ContentValues ( ) ;
values . put ( DEFAULT_SUBSCRIPTION_ID , defaultSubscriptionId ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2016-02-06 01:10:33 +01:00
}
2019-08-07 20:22:51 +02:00
public void setForceSmsSelection ( @NonNull RecipientId id , boolean forceSmsSelection ) {
2019-04-12 21:22:38 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( FORCE_SMS_SELECTION , forceSmsSelection ? 1 : 0 ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2019-04-12 21:22:38 +02:00
}
2019-08-07 20:22:51 +02:00
public void setBlocked ( @NonNull RecipientId id , boolean blocked ) {
2015-06-09 16:37:20 +02:00
ContentValues values = new ContentValues ( ) ;
2019-08-07 20:22:51 +02:00
values . put ( BLOCKED , blocked ? 1 : 0 ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2015-06-09 16:37:20 +02:00
}
2019-08-07 20:22:51 +02:00
public void setMessageRingtone ( @NonNull RecipientId id , @Nullable Uri notification ) {
2015-06-09 16:37:20 +02:00
ContentValues values = new ContentValues ( ) ;
2019-08-07 20:22:51 +02:00
values . put ( MESSAGE_RINGTONE , notification = = null ? null : notification . toString ( ) ) ;
2019-12-05 18:12:42 +01:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2015-06-09 16:37:20 +02:00
}
2019-08-07 20:22:51 +02:00
public void setCallRingtone ( @NonNull RecipientId id , @Nullable Uri ringtone ) {
2018-02-16 20:10:35 +01:00
ContentValues values = new ContentValues ( ) ;
values . put ( CALL_RINGTONE , ringtone = = null ? null : ringtone . toString ( ) ) ;
2019-12-05 18:12:42 +01:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2018-02-16 20:10:35 +01:00
}
2019-08-07 20:22:51 +02:00
public void setMessageVibrate ( @NonNull RecipientId id , @NonNull VibrateState enabled ) {
2015-06-09 16:37:20 +02:00
ContentValues values = new ContentValues ( ) ;
2019-08-07 20:22:51 +02:00
values . put ( MESSAGE_VIBRATE , enabled . getId ( ) ) ;
2019-12-05 18:12:42 +01:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2018-02-16 20:10:35 +01:00
}
2019-08-07 20:22:51 +02:00
public void setCallVibrate ( @NonNull RecipientId id , @NonNull VibrateState enabled ) {
2018-02-16 20:10:35 +01:00
ContentValues values = new ContentValues ( ) ;
values . put ( CALL_VIBRATE , enabled . getId ( ) ) ;
2019-12-05 18:12:42 +01:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2015-06-09 16:37:20 +02:00
}
2019-08-07 20:22:51 +02:00
public void setMuted ( @NonNull RecipientId id , long until ) {
2015-06-09 16:37:20 +02:00
ContentValues values = new ContentValues ( ) ;
values . put ( MUTE_UNTIL , until ) ;
2019-12-05 18:12:42 +01:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2015-06-09 16:37:20 +02:00
}
2019-11-12 15:18:57 +01:00
public void setSeenFirstInviteReminder ( @NonNull RecipientId id ) {
setInsightsBannerTier ( id , InsightsBannerTier . TIER_ONE ) ;
}
public void setSeenSecondInviteReminder ( @NonNull RecipientId id ) {
setInsightsBannerTier ( id , InsightsBannerTier . TIER_TWO ) ;
}
public void setHasSentInvite ( @NonNull RecipientId id ) {
setSeenSecondInviteReminder ( id ) ;
}
private void setInsightsBannerTier ( @NonNull RecipientId id , @NonNull InsightsBannerTier insightsBannerTier ) {
SQLiteDatabase database = databaseHelper . getWritableDatabase ( ) ;
ContentValues values = new ContentValues ( 1 ) ;
String query = ID + " = ? AND " + SEEN_INVITE_REMINDER + " < ?" ;
String [ ] args = new String [ ] { id . serialize ( ) , String . valueOf ( insightsBannerTier ) } ;
values . put ( SEEN_INVITE_REMINDER , insightsBannerTier . id ) ;
database . update ( TABLE_NAME , values , query , args ) ;
2019-08-07 20:22:51 +02:00
Recipient . live ( id ) . refresh ( ) ;
2015-10-14 06:44:01 +02:00
}
2019-08-07 20:22:51 +02:00
public void setExpireMessages ( @NonNull RecipientId id , int expiration ) {
2016-08-16 05:23:56 +02:00
ContentValues values = new ContentValues ( 1 ) ;
2019-08-07 20:22:51 +02:00
values . put ( MESSAGE_EXPIRATION_TIME , expiration ) ;
update ( id , values ) ;
Recipient . live ( id ) . refresh ( ) ;
2017-08-08 00:31:12 +02:00
}
2019-08-07 20:22:51 +02:00
public void setUnidentifiedAccessMode ( @NonNull RecipientId id , @NonNull UnidentifiedAccessMode unidentifiedAccessMode ) {
2018-05-22 11:13:10 +02:00
ContentValues values = new ContentValues ( 1 ) ;
values . put ( UNIDENTIFIED_ACCESS_MODE , unidentifiedAccessMode . getMode ( ) ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
}
2019-08-07 20:22:51 +02:00
Recipient . live ( id ) . refresh ( ) ;
2018-05-22 11:13:10 +02:00
}
2019-09-07 05:40:06 +02:00
public void setUuidSupported ( @NonNull RecipientId id , boolean supported ) {
ContentValues values = new ContentValues ( 1 ) ;
values . put ( UUID_SUPPORTED , supported ? "1" : "0" ) ;
update ( id , values ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2019-08-07 20:22:51 +02:00
public void setProfileKey ( @NonNull RecipientId id , @Nullable byte [ ] profileKey ) {
2017-08-15 03:11:13 +02:00
ContentValues values = new ContentValues ( 1 ) ;
values . put ( PROFILE_KEY , profileKey = = null ? null : Base64 . encodeBytes ( profileKey ) ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2017-08-15 03:11:13 +02:00
}
2019-12-20 21:12:22 +01:00
public void setProfileName ( @NonNull RecipientId id , @NonNull ProfileName profileName ) {
2017-08-15 03:11:13 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
2019-12-20 21:12:22 +01:00
contentValues . put ( PROFILE_GIVEN_NAME , profileName . getGivenName ( ) ) ;
contentValues . put ( PROFILE_FAMILY_NAME , profileName . getFamilyName ( ) ) ;
contentValues . put ( PROFILE_JOINED_NAME , profileName . toString ( ) ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2017-08-15 03:11:13 +02:00
}
2019-08-07 20:22:51 +02:00
public void setProfileAvatar ( @NonNull RecipientId id , @Nullable String profileAvatar ) {
2017-08-15 03:11:13 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( SIGNAL_PROFILE_AVATAR , profileAvatar ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2017-08-15 03:11:13 +02:00
}
2019-08-07 20:22:51 +02:00
public void setProfileSharing ( @NonNull RecipientId id , @SuppressWarnings ( "SameParameterValue" ) boolean enabled ) {
2017-08-17 06:49:41 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( PROFILE_SHARING , enabled ? 1 : 0 ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2017-08-17 06:49:41 +02:00
}
2019-08-07 20:22:51 +02:00
public void setNotificationChannel ( @NonNull RecipientId id , @Nullable String notificationChannel ) {
2018-08-16 18:47:43 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( NOTIFICATION_CHANNEL , notificationChannel ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2018-08-16 18:47:43 +02:00
}
2019-09-07 05:40:06 +02:00
public void setPhoneNumber ( @NonNull RecipientId id , @NonNull String e164 ) {
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( PHONE , e164 ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-07 05:40:06 +02:00
}
2019-10-29 01:16:11 +01:00
public void setUsername ( @NonNull RecipientId id , @Nullable String username ) {
if ( username ! = null ) {
Optional < RecipientId > existingUsername = getByUsername ( username ) ;
if ( existingUsername . isPresent ( ) & & ! id . equals ( existingUsername . get ( ) ) ) {
Log . i ( TAG , "Username was previously thought to be owned by " + existingUsername . get ( ) + ". Clearing their username." ) ;
setUsername ( existingUsername . get ( ) , null ) ;
}
}
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( USERNAME , username ) ;
update ( id , contentValues ) ;
Recipient . live ( id ) . refresh ( ) ;
}
public void clearUsernameIfExists ( @NonNull String username ) {
Optional < RecipientId > existingUsername = getByUsername ( username ) ;
if ( existingUsername . isPresent ( ) ) {
setUsername ( existingUsername . get ( ) , null ) ;
}
}
2019-09-07 05:40:06 +02:00
public Set < String > getAllPhoneNumbers ( ) {
2017-08-07 23:24:53 +02:00
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
2019-09-07 05:40:06 +02:00
Set < String > results = new HashSet < > ( ) ;
2017-08-07 23:24:53 +02:00
2019-09-07 05:40:06 +02:00
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { PHONE } , null , null , null , null , null ) ) {
2017-08-07 23:24:53 +02:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2019-09-07 05:40:06 +02:00
String number = cursor . getString ( cursor . getColumnIndexOrThrow ( PHONE ) ) ;
if ( ! TextUtils . isEmpty ( number ) ) {
results . add ( number ) ;
}
2017-08-07 23:24:53 +02:00
}
}
return results ;
}
2019-09-07 05:40:06 +02:00
public void markRegistered ( @NonNull RecipientId id , @NonNull UUID uuid ) {
2019-09-26 16:12:51 +02:00
ContentValues contentValues = new ContentValues ( 3 ) ;
2019-09-07 05:40:06 +02:00
contentValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
contentValues . put ( UUID , uuid . toString ( ) . toLowerCase ( ) ) ;
2019-09-26 16:12:51 +02:00
contentValues . put ( STORAGE_SERVICE_KEY , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . INSERT ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-07 05:40:06 +02:00
}
/ * *
* Marks the user as registered without providing a UUID . This should only be used when one
* cannot be reasonably obtained . { @link # markRegistered ( RecipientId , UUID ) } should be strongly
* preferred .
* /
public void markRegistered ( @NonNull RecipientId id ) {
ContentValues contentValues = new ContentValues ( 2 ) ;
contentValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
2019-09-26 16:12:51 +02:00
contentValues . put ( STORAGE_SERVICE_KEY , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . INSERT ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-07 05:40:06 +02:00
}
public void markUnregistered ( @NonNull RecipientId id ) {
ContentValues contentValues = new ContentValues ( 2 ) ;
contentValues . put ( REGISTERED , RegisteredState . NOT_REGISTERED . getId ( ) ) ;
contentValues . put ( UUID , ( String ) null ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , contentValues ) ) {
markDirty ( id , DirtyState . DELETE ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-07 05:40:06 +02:00
}
public void bulkUpdatedRegisteredStatus ( @NonNull Map < RecipientId , String > registered , Collection < RecipientId > unregistered ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
for ( Map . Entry < RecipientId , String > entry : registered . entrySet ( ) ) {
ContentValues values = new ContentValues ( 2 ) ;
values . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
values . put ( UUID , entry . getValue ( ) . toLowerCase ( ) ) ;
2019-09-26 16:12:51 +02:00
if ( update ( entry . getKey ( ) , values ) ) {
markDirty ( entry . getKey ( ) , DirtyState . INSERT ) ;
}
2019-09-07 05:40:06 +02:00
}
for ( RecipientId id : unregistered ) {
ContentValues values = new ContentValues ( 1 ) ;
values . put ( REGISTERED , RegisteredState . NOT_REGISTERED . getId ( ) ) ;
values . put ( UUID , ( String ) null ) ;
2019-09-26 16:12:51 +02:00
if ( update ( id , values ) ) {
markDirty ( id , DirtyState . DELETE ) ;
}
2019-09-07 05:40:06 +02:00
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
@Deprecated
2019-08-07 20:22:51 +02:00
public void setRegistered ( @NonNull RecipientId id , RegisteredState registeredState ) {
2017-08-22 19:44:04 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( REGISTERED , registeredState . getId ( ) ) ;
2019-08-07 20:22:51 +02:00
update ( id , contentValues ) ;
Recipient . live ( id ) . refresh ( ) ;
2017-08-22 19:44:04 +02:00
}
2017-08-07 23:24:53 +02:00
2019-09-07 05:40:06 +02:00
@Deprecated
public void setRegistered ( @NonNull Collection < RecipientId > activeIds ,
@NonNull Collection < RecipientId > inactiveIds )
2017-08-22 19:44:04 +02:00
{
2019-08-07 20:22:51 +02:00
for ( RecipientId activeId : activeIds ) {
2017-08-16 04:23:42 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
2017-08-22 19:44:04 +02:00
contentValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
2017-08-07 23:24:53 +02:00
2019-09-26 16:12:51 +02:00
if ( update ( activeId , contentValues ) ) {
2019-10-03 02:13:49 +02:00
Recipient . live ( activeId ) . refresh ( ) ;
}
2017-08-07 23:24:53 +02:00
}
2019-08-07 20:22:51 +02:00
for ( RecipientId inactiveId : inactiveIds ) {
2017-08-16 04:23:42 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
2017-08-22 19:44:04 +02:00
contentValues . put ( REGISTERED , RegisteredState . NOT_REGISTERED . getId ( ) ) ;
2017-08-07 23:24:53 +02:00
2019-09-26 16:12:51 +02:00
if ( update ( inactiveId , contentValues ) ) {
2019-10-03 02:13:49 +02:00
Recipient . live ( inactiveId ) . refresh ( ) ;
}
2017-08-07 23:24:53 +02:00
}
}
2019-11-12 15:18:57 +01:00
public @NonNull List < RecipientId > getUninvitedRecipientsForInsights ( ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
List < RecipientId > results = new LinkedList < > ( ) ;
2019-11-15 21:33:54 +01:00
final String [ ] args = new String [ ] { String . valueOf ( System . currentTimeMillis ( ) - TimeUnit . DAYS . toMillis ( 31 ) ) } ;
2019-11-12 15:18:57 +01:00
2019-11-15 21:33:54 +01:00
try ( Cursor cursor = db . rawQuery ( INSIGHTS_INVITEE_LIST , args ) ) {
2019-11-12 15:18:57 +01:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
results . add ( RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ) ;
}
}
return results ;
}
public @NonNull List < RecipientId > getRegistered ( ) {
2019-08-07 20:22:51 +02:00
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
List < RecipientId > results = new LinkedList < > ( ) ;
2017-08-07 23:24:53 +02:00
2019-08-07 20:22:51 +02:00
try ( Cursor cursor = db . query ( TABLE_NAME , ID_PROJECTION , REGISTERED + " = ?" , new String [ ] { "1" } , null , null , null ) ) {
2017-08-07 23:24:53 +02:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2019-08-07 20:22:51 +02:00
results . add ( RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ) ;
2017-08-07 23:24:53 +02:00
}
}
return results ;
}
2019-08-07 20:22:51 +02:00
public List < RecipientId > getSystemContacts ( ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
List < RecipientId > results = new LinkedList < > ( ) ;
2017-12-07 20:53:17 +01:00
2019-08-07 20:22:51 +02:00
try ( Cursor cursor = db . query ( TABLE_NAME , ID_PROJECTION , SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"" , null , null , null , null ) ) {
2017-12-07 20:53:17 +01:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2019-08-07 20:22:51 +02:00
results . add ( RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ) ;
2017-12-07 20:53:17 +01:00
}
}
return results ;
}
2018-09-26 00:41:42 +02:00
public void updateSystemContactColors ( @NonNull ColorUpdater updater ) {
2019-08-07 20:22:51 +02:00
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
Map < RecipientId , MaterialColor > updates = new HashMap < > ( ) ;
2018-09-26 00:41:42 +02:00
db . beginTransaction ( ) ;
2019-08-07 20:22:51 +02:00
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { ID , COLOR , SYSTEM_DISPLAY_NAME } , SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"" , null , null , null , null ) ) {
2018-09-26 00:41:42 +02:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2019-08-07 20:22:51 +02:00
long id = cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ;
2018-09-26 00:41:42 +02:00
MaterialColor newColor = updater . update ( cursor . getString ( cursor . getColumnIndexOrThrow ( SYSTEM_DISPLAY_NAME ) ) ,
cursor . getString ( cursor . getColumnIndexOrThrow ( COLOR ) ) ) ;
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( COLOR , newColor . serialize ( ) ) ;
2019-08-07 20:22:51 +02:00
db . update ( TABLE_NAME , contentValues , ID + " = ?" , new String [ ] { String . valueOf ( id ) } ) ;
2018-09-26 00:41:42 +02:00
2019-08-07 20:22:51 +02:00
updates . put ( RecipientId . from ( id ) , newColor ) ;
2018-09-26 00:41:42 +02:00
}
} finally {
db . setTransactionSuccessful ( ) ;
db . endTransaction ( ) ;
2019-08-07 20:22:51 +02:00
Stream . of ( updates . entrySet ( ) ) . forEach ( entry - > Recipient . live ( entry . getKey ( ) ) . refresh ( ) ) ;
2018-09-26 00:41:42 +02:00
}
}
2019-08-27 00:09:01 +02:00
public @Nullable Cursor getSignalContacts ( ) {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
2019-12-20 21:12:22 +01:00
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + SEARCH_PROFILE_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)" ;
2019-08-27 00:09:01 +02:00
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" } ;
2019-12-20 21:12:22 +01:00
String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE ;
2019-08-27 00:09:01 +02:00
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , orderBy ) ;
}
public @Nullable Cursor querySignalContacts ( @NonNull String query ) {
query = TextUtils . isEmpty ( query ) ? "*" : query ;
query = "%" + query + "%" ;
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
2019-10-29 01:16:11 +01:00
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ? OR " + USERNAME + " NOT NULL) AND " +
2019-08-27 00:09:01 +02:00
"(" +
PHONE + " LIKE ? OR " +
SYSTEM_DISPLAY_NAME + " LIKE ? OR " +
2019-12-20 21:12:22 +01:00
SEARCH_PROFILE_NAME + " LIKE ? OR " +
2019-10-29 01:16:11 +01:00
USERNAME + " LIKE ?" +
2019-08-27 00:09:01 +02:00
")" ;
2019-10-29 01:16:11 +01:00
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" , query , query , query , query } ;
2019-12-20 21:12:22 +01:00
String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE ;
2019-08-27 00:09:01 +02:00
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , orderBy ) ;
}
public @Nullable Cursor getNonSignalContacts ( ) {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " != ? AND " +
GROUP_ID + " IS NULL AND " +
SYSTEM_DISPLAY_NAME + " NOT NULL AND " +
"(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL)" ;
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) } ;
String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE ;
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , orderBy ) ;
}
public @Nullable Cursor queryNonSignalContacts ( @NonNull String query ) {
query = TextUtils . isEmpty ( query ) ? "*" : query ;
query = "%" + query + "%" ;
String selection = BLOCKED + " = ? AND " +
REGISTERED + " != ? AND " +
GROUP_ID + " IS NULL AND " +
2019-10-08 22:38:20 +02:00
SYSTEM_DISPLAY_NAME + " NOT NULL AND " +
2019-08-27 00:09:01 +02:00
"(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL) AND " +
"(" +
PHONE + " LIKE ? OR " +
2019-11-14 20:35:08 +01:00
EMAIL + " LIKE ? OR " +
2019-08-27 00:09:01 +02:00
SYSTEM_DISPLAY_NAME + " LIKE ?" +
")" ;
2019-11-14 20:35:08 +01:00
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , query , query , query } ;
2019-08-27 00:09:01 +02:00
String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE ;
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , orderBy ) ;
}
2018-09-26 00:41:42 +02:00
2019-11-14 20:35:08 +01:00
public @Nullable Cursor queryAllContacts ( @NonNull String query ) {
query = TextUtils . isEmpty ( query ) ? "*" : query ;
query = "%" + query + "%" ;
String selection = BLOCKED + " = ? AND " +
"(" +
SYSTEM_DISPLAY_NAME + " LIKE ? OR " +
2019-12-20 21:12:22 +01:00
SEARCH_PROFILE_NAME + " LIKE ? OR " +
2019-11-14 20:35:08 +01:00
PHONE + " LIKE ? OR " +
EMAIL + " LIKE ?" +
")" ;
String [ ] args = new String [ ] { "0" , query , query , query , query } ;
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , null ) ;
}
2019-12-18 06:44:21 +01:00
public @NonNull List < Recipient > getRecipientsForMultiDeviceSync ( ) {
String subquery = "SELECT " + ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . RECIPIENT_ID + " FROM " + ThreadDatabase . TABLE_NAME ;
String selection = REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
ID + " != ? AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + ID + " IN (" + subquery + "))" ;
String [ ] args = new String [ ] { String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
List < Recipient > recipients = new ArrayList < > ( ) ;
try ( Cursor cursor = databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , ID_PROJECTION , selection , args , null , null , null ) ) {
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
recipients . add ( Recipient . resolved ( RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ) ) ;
}
}
return recipients ;
}
2019-11-08 21:33:10 +01:00
public void applyBlockedUpdate ( @NonNull List < SignalServiceAddress > blocked , List < byte [ ] > groupIds ) {
List < String > blockedE164 = Stream . of ( blocked )
. filter ( b - > b . getNumber ( ) . isPresent ( ) )
. map ( b - > b . getNumber ( ) . get ( ) )
. toList ( ) ;
List < String > blockedUuid = Stream . of ( blocked )
. filter ( b - > b . getUuid ( ) . isPresent ( ) )
. map ( b - > b . getUuid ( ) . get ( ) . toString ( ) . toLowerCase ( ) )
. toList ( ) ;
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
ContentValues resetBlocked = new ContentValues ( ) ;
resetBlocked . put ( BLOCKED , 0 ) ;
db . update ( TABLE_NAME , resetBlocked , null , null ) ;
ContentValues setBlocked = new ContentValues ( ) ;
setBlocked . put ( BLOCKED , 1 ) ;
for ( String e164 : blockedE164 ) {
db . update ( TABLE_NAME , setBlocked , PHONE + " = ?" , new String [ ] { e164 } ) ;
}
for ( String uuid : blockedUuid ) {
db . update ( TABLE_NAME , setBlocked , UUID + " = ?" , new String [ ] { uuid } ) ;
}
List < String > groupIdStrings = Stream . of ( groupIds ) . map ( g - > GroupUtil . getEncodedId ( g , false ) ) . toList ( ) ;
for ( String groupId : groupIdStrings ) {
db . update ( TABLE_NAME , setBlocked , GROUP_ID + " = ?" , new String [ ] { groupId } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
ApplicationDependencies . getRecipientCache ( ) . clear ( ) ;
}
2019-09-26 16:12:51 +02:00
public void updateStorageKeys ( @NonNull Map < RecipientId , byte [ ] > keys ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
2019-11-08 21:33:10 +01:00
2019-09-26 16:12:51 +02:00
try {
for ( Map . Entry < RecipientId , byte [ ] > entry : keys . entrySet ( ) ) {
ContentValues values = new ContentValues ( ) ;
values . put ( STORAGE_SERVICE_KEY , Base64 . encodeBytes ( entry . getValue ( ) ) ) ;
db . update ( TABLE_NAME , values , ID_WHERE , new String [ ] { entry . getKey ( ) . serialize ( ) } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
public void clearDirtyState ( @NonNull List < RecipientId > recipients ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
ContentValues values = new ContentValues ( ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
for ( RecipientId id : recipients ) {
db . update ( TABLE_NAME , values , ID_WHERE , new String [ ] { id . serialize ( ) } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
void markDirty ( @NonNull RecipientId recipientId , @NonNull DirtyState dirtyState ) {
2019-12-19 23:41:21 +01:00
if ( ! FeatureFlags . storageService ( ) ) return ;
2019-09-26 16:12:51 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( DIRTY , dirtyState . getId ( ) ) ;
String query = ID + " = ? AND (" + UUID + " NOT NULL OR " + PHONE + " NOT NULL) AND " + DIRTY + " < ?" ;
String [ ] args = new String [ ] { recipientId . serialize ( ) , String . valueOf ( dirtyState . id ) } ;
databaseHelper . getWritableDatabase ( ) . update ( TABLE_NAME , contentValues , query , args ) ;
}
/ * *
* Will update the database with the content values you specified . It will make an intelligent
* query such that this will only return true if a row was * actually * updated .
* /
private boolean update ( @NonNull RecipientId id , ContentValues contentValues ) {
2019-12-05 17:41:47 +01:00
SQLiteDatabase database = databaseHelper . getWritableDatabase ( ) ;
String selection = ID + " = ?" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
2019-09-26 16:12:51 +02:00
2019-12-05 17:41:47 +01:00
Pair < String , String [ ] > result = SqlUtil . buildTrueUpdateQuery ( selection , args , contentValues ) ;
2019-09-26 16:12:51 +02:00
2019-12-05 17:41:47 +01:00
return database . update ( TABLE_NAME , contentValues , result . first ( ) , result . second ( ) ) > 0 ;
2017-08-16 04:23:42 +02:00
}
2015-06-09 16:37:20 +02:00
2019-10-07 22:36:05 +02:00
private @NonNull Optional < RecipientId > getByColumn ( @NonNull String column , String value ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
String query = column + " = ?" ;
String [ ] args = new String [ ] { value } ;
try ( Cursor cursor = db . query ( TABLE_NAME , ID_PROJECTION , query , args , null , null , null ) ) {
if ( cursor ! = null & & cursor . moveToFirst ( ) ) {
return Optional . of ( RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ) ;
} else {
return Optional . absent ( ) ;
}
}
}
private @NonNull RecipientId getOrInsertByColumn ( @NonNull String column , String value ) {
if ( TextUtils . isEmpty ( value ) ) {
throw new AssertionError ( column + " cannot be empty." ) ;
}
Optional < RecipientId > existing = getByColumn ( column , value ) ;
if ( existing . isPresent ( ) ) {
return existing . get ( ) ;
} else {
ContentValues values = new ContentValues ( ) ;
values . put ( column , value ) ;
long id = databaseHelper . getWritableDatabase ( ) . insert ( TABLE_NAME , null , values ) ;
if ( id < 0 ) {
existing = getByColumn ( column , value ) ;
if ( existing . isPresent ( ) ) {
return existing . get ( ) ;
} else {
throw new AssertionError ( "Failed to insert recipient!" ) ;
}
2019-10-09 04:14:52 +02:00
} else {
return RecipientId . from ( id ) ;
2019-10-07 22:36:05 +02:00
}
}
}
2017-08-16 04:23:42 +02:00
public class BulkOperationsHandle {
2015-06-11 22:00:50 +02:00
2017-08-16 04:23:42 +02:00
private final SQLiteDatabase database ;
2019-08-07 20:22:51 +02:00
private final Map < RecipientId , PendingContactInfo > pendingContactInfoMap = new HashMap < > ( ) ;
2017-08-22 19:44:04 +02:00
BulkOperationsHandle ( SQLiteDatabase database ) {
2017-08-16 04:23:42 +02:00
this . database = database ;
}
2019-08-27 00:09:01 +02:00
public void setSystemContactInfo ( @NonNull RecipientId id ,
@Nullable String displayName ,
@Nullable String photoUri ,
@Nullable String systemPhoneLabel ,
int systemPhoneType ,
@Nullable String systemContactUri )
{
2019-09-26 16:12:51 +02:00
ContentValues dirtyQualifyingValues = new ContentValues ( ) ;
dirtyQualifyingValues . put ( SYSTEM_DISPLAY_NAME , displayName ) ;
if ( update ( id , dirtyQualifyingValues ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
}
ContentValues refreshQualifyingValues = new ContentValues ( ) ;
refreshQualifyingValues . put ( SYSTEM_PHOTO_URI , photoUri ) ;
refreshQualifyingValues . put ( SYSTEM_PHONE_LABEL , systemPhoneLabel ) ;
refreshQualifyingValues . put ( SYSTEM_PHONE_TYPE , systemPhoneType ) ;
refreshQualifyingValues . put ( SYSTEM_CONTACT_URI , systemContactUri ) ;
if ( update ( id , refreshQualifyingValues ) ) {
pendingContactInfoMap . put ( id , new PendingContactInfo ( displayName , photoUri , systemPhoneLabel , systemContactUri ) ) ;
}
2017-11-26 19:45:39 +01:00
2019-09-26 16:12:51 +02:00
ContentValues otherValues = new ContentValues ( ) ;
otherValues . put ( SYSTEM_INFO_PENDING , 0 ) ;
update ( id , otherValues ) ;
2017-08-16 04:23:42 +02:00
}
public void finish ( ) {
2019-09-26 16:12:51 +02:00
markAllRelevantEntriesDirty ( ) ;
clearSystemDataForPendingInfo ( ) ;
2017-08-16 04:23:42 +02:00
database . setTransactionSuccessful ( ) ;
database . endTransaction ( ) ;
2017-08-22 19:44:04 +02:00
2019-08-07 20:22:51 +02:00
Stream . of ( pendingContactInfoMap . entrySet ( ) ) . forEach ( entry - > Recipient . live ( entry . getKey ( ) ) . refresh ( ) ) ;
2017-08-16 04:23:42 +02:00
}
2019-09-26 16:12:51 +02:00
private void markAllRelevantEntriesDirty ( ) {
String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_KEY + " NOT NULL AND " + DIRTY + " < ?" ;
String [ ] args = new String [ ] { "1" , String . valueOf ( DirtyState . UPDATE . getId ( ) ) } ;
ContentValues values = new ContentValues ( 1 ) ;
values . put ( DIRTY , DirtyState . UPDATE . getId ( ) ) ;
database . update ( TABLE_NAME , values , query , args ) ;
}
private void clearSystemDataForPendingInfo ( ) {
String query = SYSTEM_INFO_PENDING + " = ?" ;
String [ ] args = new String [ ] { "1" } ;
ContentValues values = new ContentValues ( 5 ) ;
values . put ( SYSTEM_INFO_PENDING , 0 ) ;
values . put ( SYSTEM_DISPLAY_NAME , ( String ) null ) ;
values . put ( SYSTEM_PHOTO_URI , ( String ) null ) ;
values . put ( SYSTEM_PHONE_LABEL , ( String ) null ) ;
values . put ( SYSTEM_CONTACT_URI , ( String ) null ) ;
database . update ( TABLE_NAME , values , query , args ) ;
}
2015-06-09 16:37:20 +02:00
}
2018-09-26 00:41:42 +02:00
public interface ColorUpdater {
MaterialColor update ( @NonNull String name , @Nullable String color ) ;
}
2017-08-22 03:47:37 +02:00
public static class RecipientSettings {
2019-09-26 16:12:51 +02:00
private final RecipientId id ;
private final UUID uuid ;
private final String username ;
private final String e164 ;
private final String email ;
private final String groupId ;
private final boolean blocked ;
private final long muteUntil ;
private final VibrateState messageVibrateState ;
private final VibrateState callVibrateState ;
private final Uri messageRingtone ;
private final Uri callRingtone ;
private final MaterialColor color ;
private final int defaultSubscriptionId ;
private final int expireMessages ;
private final RegisteredState registered ;
private final byte [ ] profileKey ;
private final String systemDisplayName ;
private final String systemContactPhoto ;
private final String systemPhoneLabel ;
private final String systemContactUri ;
2019-12-20 21:12:22 +01:00
private final ProfileName signalProfileName ;
2019-09-26 16:12:51 +02:00
private final String signalProfileAvatar ;
private final boolean profileSharing ;
private final String notificationChannel ;
private final UnidentifiedAccessMode unidentifiedAccessMode ;
private final boolean forceSmsSelection ;
private final boolean uuidSupported ;
private final InsightsBannerTier insightsBannerTier ;
private final byte [ ] storageKey ;
private final byte [ ] identityKey ;
private final IdentityDatabase . VerifiedStatus identityStatus ;
2015-06-30 18:16:05 +02:00
2019-08-07 20:22:51 +02:00
RecipientSettings ( @NonNull RecipientId id ,
2019-09-07 05:40:06 +02:00
@Nullable UUID uuid ,
2019-10-29 01:16:11 +01:00
@Nullable String username ,
2019-09-07 05:40:06 +02:00
@Nullable String e164 ,
@Nullable String email ,
@Nullable String groupId ,
boolean blocked , long muteUntil ,
2018-02-16 20:10:35 +01:00
@NonNull VibrateState messageVibrateState ,
@NonNull VibrateState callVibrateState ,
@Nullable Uri messageRingtone ,
@Nullable Uri callRingtone ,
2017-08-22 03:47:37 +02:00
@Nullable MaterialColor color ,
int defaultSubscriptionId ,
int expireMessages ,
2017-08-22 19:44:04 +02:00
@NonNull RegisteredState registered ,
2017-08-22 03:47:37 +02:00
@Nullable byte [ ] profileKey ,
@Nullable String systemDisplayName ,
2017-11-26 19:45:39 +01:00
@Nullable String systemContactPhoto ,
@Nullable String systemPhoneLabel ,
@Nullable String systemContactUri ,
2019-12-20 21:12:22 +01:00
@NonNull ProfileName signalProfileName ,
2017-08-22 03:47:37 +02:00
@Nullable String signalProfileAvatar ,
2018-08-16 18:47:43 +02:00
boolean profileSharing ,
2018-05-22 11:13:10 +02:00
@Nullable String notificationChannel ,
2019-04-12 21:22:38 +02:00
@NonNull UnidentifiedAccessMode unidentifiedAccessMode ,
2019-09-07 05:40:06 +02:00
boolean forceSmsSelection ,
2019-11-12 15:18:57 +01:00
boolean uuidSupported ,
2019-09-26 16:12:51 +02:00
@NonNull InsightsBannerTier insightsBannerTier ,
@Nullable byte [ ] storageKey ,
@Nullable byte [ ] identityKey ,
@NonNull IdentityDatabase . VerifiedStatus identityStatus )
2015-06-24 00:10:50 +02:00
{
2019-08-07 20:22:51 +02:00
this . id = id ;
2019-09-07 05:40:06 +02:00
this . uuid = uuid ;
2019-10-29 01:16:11 +01:00
this . username = username ;
2019-09-07 05:40:06 +02:00
this . e164 = e164 ;
this . email = email ;
this . groupId = groupId ;
2018-05-22 11:13:10 +02:00
this . blocked = blocked ;
this . muteUntil = muteUntil ;
this . messageVibrateState = messageVibrateState ;
this . callVibrateState = callVibrateState ;
this . messageRingtone = messageRingtone ;
this . callRingtone = callRingtone ;
this . color = color ;
this . defaultSubscriptionId = defaultSubscriptionId ;
this . expireMessages = expireMessages ;
this . registered = registered ;
this . profileKey = profileKey ;
this . systemDisplayName = systemDisplayName ;
this . systemContactPhoto = systemContactPhoto ;
this . systemPhoneLabel = systemPhoneLabel ;
this . systemContactUri = systemContactUri ;
this . signalProfileName = signalProfileName ;
this . signalProfileAvatar = signalProfileAvatar ;
this . profileSharing = profileSharing ;
this . notificationChannel = notificationChannel ;
this . unidentifiedAccessMode = unidentifiedAccessMode ;
2019-04-12 21:22:38 +02:00
this . forceSmsSelection = forceSmsSelection ;
2019-09-07 05:40:06 +02:00
this . uuidSupported = uuidSupported ;
2019-09-26 16:12:51 +02:00
this . insightsBannerTier = insightsBannerTier ;
this . storageKey = storageKey ;
this . identityKey = identityKey ;
this . identityStatus = identityStatus ;
2015-06-24 00:10:50 +02:00
}
2019-08-07 20:22:51 +02:00
public RecipientId getId ( ) {
return id ;
}
2019-09-07 05:40:06 +02:00
public @Nullable UUID getUuid ( ) {
return uuid ;
}
2019-10-29 01:16:11 +01:00
public @Nullable String getUsername ( ) {
return username ;
}
2019-09-07 05:40:06 +02:00
public @Nullable String getE164 ( ) {
return e164 ;
}
public @Nullable String getEmail ( ) {
return email ;
}
public @Nullable String getGroupId ( ) {
return groupId ;
2019-08-07 20:22:51 +02:00
}
2015-06-30 18:16:05 +02:00
public @Nullable MaterialColor getColor ( ) {
2015-06-24 00:10:50 +02:00
return color ;
2015-06-09 16:37:20 +02:00
}
public boolean isBlocked ( ) {
return blocked ;
}
public long getMuteUntil ( ) {
return muteUntil ;
}
2018-02-16 20:10:35 +01:00
public @NonNull VibrateState getMessageVibrateState ( ) {
return messageVibrateState ;
}
public @NonNull VibrateState getCallVibrateState ( ) {
return callVibrateState ;
}
public @Nullable Uri getMessageRingtone ( ) {
return messageRingtone ;
2015-06-09 16:37:20 +02:00
}
2018-02-16 20:10:35 +01:00
public @Nullable Uri getCallRingtone ( ) {
return callRingtone ;
2015-06-09 16:37:20 +02:00
}
2015-10-14 06:44:01 +02:00
2019-11-12 15:18:57 +01:00
public @NonNull InsightsBannerTier getInsightsBannerTier ( ) {
return insightsBannerTier ;
2015-10-14 06:44:01 +02:00
}
2016-02-06 01:10:33 +01:00
public Optional < Integer > getDefaultSubscriptionId ( ) {
2017-10-16 22:11:42 +02:00
return defaultSubscriptionId ! = - 1 ? Optional . of ( defaultSubscriptionId ) : Optional . absent ( ) ;
2016-02-06 01:10:33 +01:00
}
2016-08-16 05:23:56 +02:00
public int getExpireMessages ( ) {
return expireMessages ;
}
2017-08-07 23:24:53 +02:00
2017-08-22 19:44:04 +02:00
public RegisteredState getRegistered ( ) {
2017-08-07 23:24:53 +02:00
return registered ;
}
2017-08-08 00:31:12 +02:00
2018-05-22 11:13:10 +02:00
public @Nullable byte [ ] getProfileKey ( ) {
2017-08-15 03:11:13 +02:00
return profileKey ;
}
public @Nullable String getSystemDisplayName ( ) {
2017-08-08 00:31:12 +02:00
return systemDisplayName ;
}
2017-08-15 03:11:13 +02:00
2017-11-26 19:45:39 +01:00
public @Nullable String getSystemContactPhotoUri ( ) {
return systemContactPhoto ;
}
public @Nullable String getSystemPhoneLabel ( ) {
return systemPhoneLabel ;
}
public @Nullable String getSystemContactUri ( ) {
return systemContactUri ;
}
2019-12-20 21:12:22 +01:00
public @NonNull ProfileName getProfileName ( ) {
2017-08-15 03:11:13 +02:00
return signalProfileName ;
}
public @Nullable String getProfileAvatar ( ) {
return signalProfileAvatar ;
}
2017-08-17 06:49:41 +02:00
public boolean isProfileSharing ( ) {
return profileSharing ;
}
2018-08-16 18:47:43 +02:00
public @Nullable String getNotificationChannel ( ) {
return notificationChannel ;
}
2018-05-22 11:13:10 +02:00
public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode ( ) {
return unidentifiedAccessMode ;
}
2019-04-12 21:22:38 +02:00
public boolean isForceSmsSelection ( ) {
return forceSmsSelection ;
}
2019-09-07 05:40:06 +02:00
public boolean isUuidSupported ( ) {
return uuidSupported ;
}
2019-09-26 16:12:51 +02:00
public @Nullable byte [ ] getStorageKey ( ) {
return storageKey ;
}
public @Nullable byte [ ] getIdentityKey ( ) {
return identityKey ;
}
public @NonNull IdentityDatabase . VerifiedStatus getIdentityStatus ( ) {
return identityStatus ;
}
2015-06-09 16:37:20 +02:00
}
2016-08-27 01:53:23 +02:00
2018-08-16 18:47:43 +02:00
public static class RecipientReader implements Closeable {
2016-08-27 01:53:23 +02:00
2018-08-16 18:47:43 +02:00
private final Cursor cursor ;
2016-08-27 01:53:23 +02:00
2019-09-26 16:12:51 +02:00
RecipientReader ( Cursor cursor ) {
2016-08-27 01:53:23 +02:00
this . cursor = cursor ;
}
2017-08-01 17:56:00 +02:00
public @NonNull Recipient getCurrent ( ) {
2019-08-07 20:22:51 +02:00
RecipientId id = RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ;
2019-09-26 00:35:17 +02:00
return Recipient . resolved ( id ) ;
2016-08-27 01:53:23 +02:00
}
2017-08-01 17:56:00 +02:00
public @Nullable Recipient getNext ( ) {
2018-09-26 00:41:42 +02:00
if ( cursor ! = null & & ! cursor . moveToNext ( ) ) {
2016-08-27 01:53:23 +02:00
return null ;
}
return getCurrent ( ) ;
}
2018-08-16 18:47:43 +02:00
public void close ( ) {
cursor . close ( ) ;
}
2016-08-27 01:53:23 +02:00
}
2017-11-26 19:45:39 +01:00
private static class PendingContactInfo {
private final String displayName ;
private final String photoUri ;
private final String phoneLabel ;
private final String contactUri ;
private PendingContactInfo ( String displayName , String photoUri , String phoneLabel , String contactUri ) {
this . displayName = displayName ;
this . photoUri = photoUri ;
this . phoneLabel = phoneLabel ;
this . contactUri = contactUri ;
}
}
2019-10-02 18:06:13 +02:00
public static class MissingRecipientError extends AssertionError {
public MissingRecipientError ( @Nullable RecipientId id ) {
super ( "Failed to find recipient with ID: " + id ) ;
}
}
2015-06-09 16:37:20 +02:00
}