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 ;
2020-02-11 00:40:22 +01:00
import org.signal.zkgroup.profiles.ProfileKey ;
import org.signal.zkgroup.profiles.ProfileKeyCredential ;
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.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 ;
2020-03-26 15:00:17 +01:00
import org.thoughtcrime.securesms.groups.GroupId ;
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 ;
2020-03-26 15:00:17 +01:00
import org.thoughtcrime.securesms.storage.StorageSyncHelper ;
import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate ;
import org.thoughtcrime.securesms.storage.StorageSyncModels ;
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.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 ;
2016-03-23 18:34:41 +01:00
import org.whispersystems.libsignal.util.guava.Optional ;
2020-02-14 17:00:32 +01:00
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile ;
2019-11-08 21:33:10 +01:00
import org.whispersystems.signalservice.api.push.SignalServiceAddress ;
2020-03-18 21:31:45 +01:00
import org.whispersystems.signalservice.api.storage.SignalAccountRecord ;
2019-09-26 16:12:51 +02:00
import org.whispersystems.signalservice.api.storage.SignalContactRecord ;
2020-02-10 19:42:43 +01:00
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record ;
2020-02-28 19:03:06 +01:00
import org.whispersystems.signalservice.api.storage.StorageId ;
2020-02-10 16:06:12 +01:00
import org.whispersystems.signalservice.api.util.UuidUtil ;
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" ;
2020-02-10 19:42:43 +01:00
private static final String GROUP_TYPE = "group_type" ;
2019-08-07 20:22:51 +02:00
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" ;
2020-02-11 00:40:22 +01:00
private static final String PROFILE_KEY_CREDENTIAL = "profile_key_credential" ;
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" ;
2020-02-14 21:47:31 +01:00
private static final String UUID_CAPABILITY = "uuid_supported" ;
2020-02-14 17:00:32 +01:00
private static final String GROUPS_V2_CAPABILITY = "gv2_capability" ;
2020-02-28 19:03:06 +01:00
private static final String STORAGE_SERVICE_ID = "storage_service_key" ;
2019-09-26 16:12:51 +02:00
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 [ ] {
2020-02-10 19:42:43 +01:00
UUID , USERNAME , PHONE , EMAIL , GROUP_ID , GROUP_TYPE ,
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 ,
2020-02-11 00:40:22 +01:00
PROFILE_KEY , PROFILE_KEY_CREDENTIAL ,
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 ,
2020-02-14 17:00:32 +01:00
FORCE_SMS_SELECTION ,
2020-02-14 21:47:31 +01:00
UUID_CAPABILITY , GROUPS_V2_CAPABILITY ,
2020-02-28 19:03:06 +01:00
STORAGE_SERVICE_ID , DIRTY
2019-09-26 16:12:51 +02:00
} ;
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 + ");" ,
2020-02-10 19:42:43 +01:00
"CREATE INDEX IF NOT EXISTS recipient_group_type_index ON " + TABLE_NAME + " (" + GROUP_TYPE + ");" ,
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 } ;
2020-03-16 17:41:48 +01:00
private static final String [ ] SEARCH_PROJECTION = new String [ ] { ID , SYSTEM_DISPLAY_NAME , PHONE , EMAIL , SYSTEM_PHONE_LABEL , SYSTEM_PHONE_TYPE , REGISTERED , "COALESCE(" + nullIfEmpty ( PROFILE_JOINED_NAME ) + ", " + nullIfEmpty ( PROFILE_GIVEN_NAME ) + ") AS " + SEARCH_PROFILE_NAME , "COALESCE(" + nullIfEmpty ( SYSTEM_DISPLAY_NAME ) + ", " + nullIfEmpty ( PROFILE_JOINED_NAME ) + ", " + nullIfEmpty ( PROFILE_GIVEN_NAME ) + ", " + nullIfEmpty ( USERNAME ) + ") AS " + SORT_NAME } ;
2019-12-20 21:12:22 +01:00
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 ] ;
}
}
2020-03-18 21:31:45 +01:00
public enum DirtyState {
2019-09-26 16:12:51 +02:00
CLEAN ( 0 ) , UPDATE ( 1 ) , INSERT ( 2 ) , DELETE ( 3 ) ;
private final int id ;
DirtyState ( int id ) {
this . id = id ;
}
int getId ( ) {
return id ;
}
2020-03-18 21:31:45 +01:00
public static DirtyState fromId ( int id ) {
return values ( ) [ id ] ;
}
2019-09-26 16:12:51 +02:00
}
2020-02-10 19:42:43 +01:00
public enum GroupType {
NONE ( 0 ) , MMS ( 1 ) , SIGNAL_V1 ( 2 ) ;
private final int id ;
GroupType ( int id ) {
this . id = id ;
}
int getId ( ) {
return id ;
}
public static GroupType fromId ( int id ) {
return values ( ) [ 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, " +
2020-02-10 19:42:43 +01:00
GROUP_TYPE + " INTEGER DEFAULT " + GroupType . NONE . getId ( ) + ", " +
2019-08-07 20:22:51 +02:00
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, " +
2020-02-11 00:40:22 +01:00
PROFILE_KEY_CREDENTIAL + " 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, " +
2020-02-14 21:47:31 +01:00
UUID_CAPABILITY + " INTEGER DEFAULT " + Recipient . Capability . UNKNOWN . serialize ( ) + ", " +
2020-02-14 17:00:32 +01:00
GROUPS_V2_CAPABILITY + " INTEGER DEFAULT " + Recipient . Capability . UNKNOWN . serialize ( ) + ", " +
2020-02-28 19:03:06 +01:00
STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " +
2020-02-10 19:42:43 +01:00
DIRTY + " INTEGER DEFAULT " + DirtyState . CLEAN . getId ( ) + ");" ;
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 ) {
2020-02-10 19:42:43 +01:00
return getOrInsertByColumn ( UUID , uuid . toString ( ) ) . recipientId ;
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 ) {
2020-02-10 19:42:43 +01:00
return getOrInsertByColumn ( PHONE , e164 ) . recipientId ;
2019-08-07 20:22:51 +02:00
}
2020-02-10 19:42:43 +01:00
public @NonNull RecipientId getOrInsertFromEmail ( @NonNull String email ) {
return getOrInsertByColumn ( EMAIL , email ) . recipientId ;
2019-08-07 20:22:51 +02:00
}
2020-03-26 15:00:17 +01:00
public @NonNull RecipientId getOrInsertFromGroupId ( @NonNull GroupId groupId ) {
GetOrInsertResult result = getOrInsertByColumn ( GROUP_ID , groupId . toString ( ) ) ;
2020-02-10 19:42:43 +01:00
if ( result . neededInsert ) {
ContentValues values = new ContentValues ( ) ;
2020-03-27 15:28:48 +01:00
if ( groupId . isMms ( ) ) {
2020-02-10 19:42:43 +01:00
values . put ( GROUP_TYPE , GroupType . MMS . getId ( ) ) ;
} else {
values . put ( GROUP_TYPE , GroupType . SIGNAL_V1 . getId ( ) ) ;
values . put ( DIRTY , DirtyState . INSERT . getId ( ) ) ;
2020-02-28 19:03:06 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
2020-02-10 19:42:43 +01:00
}
update ( result . recipientId , values ) ;
}
return result . recipientId ;
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
}
}
}
2020-03-18 21:31:45 +01:00
public @NonNull DirtyState getDirtyState ( @NonNull RecipientId recipientId ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { DIRTY } , ID_WHERE , new String [ ] { recipientId . serialize ( ) } , null , null , null ) ) {
if ( cursor ! = null & & cursor . moveToFirst ( ) ) {
return DirtyState . fromId ( cursor . getInt ( cursor . getColumnIndexOrThrow ( DIRTY ) ) ) ;
}
}
return DirtyState . CLEAN ;
}
2019-09-26 16:12:51 +02:00
public @NonNull List < RecipientSettings > getPendingRecipientSyncUpdates ( ) {
2020-03-18 21:31:45 +01:00
String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
String [ ] args = new String [ ] { String . valueOf ( DirtyState . UPDATE . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
return getRecipientSettings ( query , args ) ;
2019-09-26 16:12:51 +02:00
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncInsertions ( ) {
2020-03-18 21:31:45 +01:00
String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
String [ ] args = new String [ ] { String . valueOf ( DirtyState . INSERT . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
return getRecipientSettings ( query , args ) ;
2019-09-26 16:12:51 +02:00
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncDeletions ( ) {
2020-03-18 21:31:45 +01:00
String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
String [ ] args = new String [ ] { String . valueOf ( DirtyState . DELETE . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
return getRecipientSettings ( query , args ) ;
2019-09-26 16:12:51 +02:00
}
2020-02-28 19:03:06 +01:00
public @Nullable RecipientSettings getByStorageId ( @NonNull byte [ ] storageId ) {
List < RecipientSettings > result = getRecipientSettings ( STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( storageId ) } ) ;
2019-09-26 16:12:51 +02:00
if ( result . size ( ) > 0 ) {
return result . get ( 0 ) ;
}
return null ;
}
2020-03-18 21:31:45 +01:00
public void markNeedsSync ( @NonNull RecipientId recipientId ) {
markDirty ( recipientId , DirtyState . UPDATE ) ;
}
2020-02-28 19:03:06 +01:00
public void applyStorageIdUpdates ( @NonNull Map < RecipientId , StorageId > storageIds ) {
2019-09-26 16:12:51 +02:00
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
String query = ID + " = ?" ;
2020-02-28 19:03:06 +01:00
for ( Map . Entry < RecipientId , StorageId > entry : storageIds . entrySet ( ) ) {
2019-09-26 16:12:51 +02:00
ContentValues values = new ContentValues ( ) ;
2020-02-28 19:03:06 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( entry . getValue ( ) . getRaw ( ) ) ) ;
2019-09-26 16:12:51 +02:00
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
db . update ( TABLE_NAME , values , query , new String [ ] { entry . getKey ( ) . serialize ( ) } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
2020-02-28 19:03:06 +01:00
public void applyStorageSyncUpdates ( @NonNull Collection < SignalContactRecord > contactInserts ,
@NonNull Collection < RecordUpdate < SignalContactRecord > > contactUpdates ,
@NonNull Collection < SignalGroupV1Record > groupV1Inserts ,
@NonNull Collection < RecordUpdate < SignalGroupV1Record > > groupV1Updates )
2019-09-26 16:12:51 +02:00
{
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
IdentityDatabase identityDatabase = DatabaseFactory . getIdentityDatabase ( context ) ;
2020-03-18 21:31:45 +01:00
ThreadDatabase threadDatabase = DatabaseFactory . getThreadDatabase ( context ) ;
2019-09-26 16:12:51 +02:00
db . beginTransaction ( ) ;
try {
2020-02-28 20:56:33 +01:00
2020-02-10 19:42:43 +01:00
for ( SignalContactRecord insert : contactInserts ) {
2020-02-28 20:56:33 +01:00
ContentValues values = getValuesForStorageContact ( insert ) ;
long id = db . insertWithOnConflict ( TABLE_NAME , null , values , SQLiteDatabase . CONFLICT_IGNORE ) ;
if ( id < 0 ) {
Log . w ( TAG , "Failed to insert! It's likely that these were newly-registered users that were missed in the merge. Doing an update instead." ) ;
if ( insert . getAddress ( ) . getNumber ( ) . isPresent ( ) ) {
int count = db . update ( TABLE_NAME , values , PHONE + " = ?" , new String [ ] { insert . getAddress ( ) . getNumber ( ) . get ( ) } ) ;
Log . w ( TAG , "Updated " + count + " users by E164." ) ;
} else {
int count = db . update ( TABLE_NAME , values , UUID + " = ?" , new String [ ] { insert . getAddress ( ) . getUuid ( ) . get ( ) . toString ( ) } ) ;
Log . w ( TAG , "Updated " + count + " users by UUID." ) ;
}
} else {
RecipientId recipientId = RecipientId . from ( id ) ;
if ( insert . getIdentityKey ( ) . isPresent ( ) ) {
try {
IdentityKey identityKey = new IdentityKey ( insert . getIdentityKey ( ) . get ( ) , 0 ) ;
2020-02-28 19:03:06 +01:00
DatabaseFactory . getIdentityDatabase ( context ) . updateIdentityAfterSync ( recipientId , identityKey , StorageSyncModels . remoteToLocalIdentityStatus ( insert . getIdentityState ( ) ) ) ;
2020-02-28 20:56:33 +01:00
IdentityUtil . markIdentityVerified ( context , Recipient . resolved ( recipientId ) , true , true ) ;
} catch ( InvalidKeyException e ) {
Log . w ( TAG , "Failed to process identity key during insert! Skipping." , e ) ;
}
2019-09-26 16:12:51 +02:00
}
2020-02-10 19:42:43 +01:00
2020-03-18 21:31:45 +01:00
threadDatabase . setArchived ( recipientId , insert . isArchived ( ) ) ;
Recipient . live ( recipientId ) . refresh ( ) ;
2020-02-10 19:42:43 +01:00
}
2019-09-26 16:12:51 +02:00
}
2020-02-28 19:03:06 +01:00
for ( RecordUpdate < SignalContactRecord > update : contactUpdates ) {
2020-02-10 19:42:43 +01:00
ContentValues values = getValuesForStorageContact ( update . getNew ( ) ) ;
2020-02-28 19:03:06 +01:00
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( update . getOld ( ) . getId ( ) . getRaw ( ) ) } ) ;
2019-09-26 16:12:51 +02:00
if ( updateCount < 1 ) {
throw new AssertionError ( "Had an update, but it didn't match any rows!" ) ;
}
2020-02-28 19:03:06 +01:00
RecipientId recipientId = getByStorageKeyOrThrow ( update . getNew ( ) . getId ( ) . getRaw ( ) ) ;
2019-09-26 16:12:51 +02:00
2020-02-28 19:03:06 +01:00
if ( StorageSyncHelper . profileKeyChanged ( update ) ) {
2020-02-11 00:40:22 +01:00
clearProfileKeyCredential ( recipientId ) ;
}
2019-09-26 16:12:51 +02:00
try {
Optional < IdentityRecord > oldIdentityRecord = identityDatabase . getIdentity ( recipientId ) ;
2020-02-10 19:42:43 +01:00
if ( update . getNew ( ) . getIdentityKey ( ) . isPresent ( ) ) {
IdentityKey identityKey = new IdentityKey ( update . getNew ( ) . getIdentityKey ( ) . get ( ) , 0 ) ;
2020-02-28 19:03:06 +01:00
DatabaseFactory . getIdentityDatabase ( context ) . updateIdentityAfterSync ( recipientId , identityKey , StorageSyncModels . remoteToLocalIdentityStatus ( update . getNew ( ) . getIdentityState ( ) ) ) ;
2020-02-10 19:42:43 +01:00
}
2019-09-26 16:12:51 +02:00
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 ) ;
}
2020-03-18 21:31:45 +01:00
threadDatabase . setArchived ( recipientId , update . getNew ( ) . isArchived ( ) ) ;
Recipient . live ( recipientId ) . refresh ( ) ;
2019-09-26 16:12:51 +02:00
}
2020-02-10 19:42:43 +01:00
for ( SignalGroupV1Record insert : groupV1Inserts ) {
db . insertOrThrow ( TABLE_NAME , null , getValuesForStorageGroupV1 ( insert ) ) ;
2020-03-18 21:31:45 +01:00
2020-03-26 15:00:17 +01:00
Recipient recipient = Recipient . externalGroup ( context , GroupId . v1 ( insert . getGroupId ( ) ) ) ;
2020-03-18 21:31:45 +01:00
threadDatabase . setArchived ( recipient . getId ( ) , insert . isArchived ( ) ) ;
recipient . live ( ) . refresh ( ) ;
2020-02-10 19:42:43 +01:00
}
2020-02-28 19:03:06 +01:00
for ( RecordUpdate < SignalGroupV1Record > update : groupV1Updates ) {
2020-02-10 19:42:43 +01:00
ContentValues values = getValuesForStorageGroupV1 ( update . getNew ( ) ) ;
2020-02-28 19:03:06 +01:00
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( update . getOld ( ) . getId ( ) . getRaw ( ) ) } ) ;
2020-02-10 19:42:43 +01:00
if ( updateCount < 1 ) {
throw new AssertionError ( "Had an update, but it didn't match any rows!" ) ;
}
2020-03-18 21:31:45 +01:00
2020-03-26 15:00:17 +01:00
Recipient recipient = Recipient . externalGroup ( context , GroupId . v1 ( update . getOld ( ) . getGroupId ( ) ) ) ;
2020-03-18 21:31:45 +01:00
threadDatabase . setArchived ( recipient . getId ( ) , update . getNew ( ) . isArchived ( ) ) ;
recipient . live ( ) . refresh ( ) ;
2020-02-10 19:42:43 +01:00
}
2019-09-26 16:12:51 +02:00
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
2020-03-18 21:31:45 +01:00
public void applyStorageSyncUpdates ( @NonNull StorageId storageId , SignalAccountRecord update ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
ContentValues values = new ContentValues ( ) ;
ProfileName profileName = ProfileName . fromParts ( update . getGivenName ( ) . orNull ( ) , update . getFamilyName ( ) . orNull ( ) ) ;
values . put ( PROFILE_GIVEN_NAME , profileName . getGivenName ( ) ) ;
values . put ( PROFILE_FAMILY_NAME , profileName . getFamilyName ( ) ) ;
values . put ( PROFILE_JOINED_NAME , profileName . toString ( ) ) ;
values . put ( PROFILE_KEY , update . getProfileKey ( ) . transform ( Base64 : : encodeBytes ) . orNull ( ) ) ;
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( update . getId ( ) . getRaw ( ) ) ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( storageId . getRaw ( ) ) } ) ;
if ( updateCount < 1 ) {
throw new AssertionError ( "Account update didn't match any rows!" ) ;
}
Recipient . self ( ) . live ( ) . refresh ( ) ;
}
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 ( ) ;
2020-02-28 19:03:06 +01:00
String query = STORAGE_SERVICE_ID + " = ?" ;
2019-09-26 16:12:51 +02:00
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 ( ) ) ;
}
2020-02-10 19:42:43 +01:00
ProfileName profileName = ProfileName . fromParts ( contact . getGivenName ( ) . orNull ( ) , contact . getFamilyName ( ) . orNull ( ) ) ;
2020-03-16 15:41:43 +01:00
String username = contact . getUsername ( ) . orNull ( ) ;
2019-12-20 21:12:22 +01:00
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 ( ) ) ;
2020-02-10 19:42:43 +01:00
values . put ( PROFILE_KEY , contact . getProfileKey ( ) . transform ( Base64 : : encodeBytes ) . orNull ( ) ) ;
2020-03-16 15:41:43 +01:00
values . put ( USERNAME , TextUtils . isEmpty ( username ) ? null : username ) ;
2019-09-26 16:12:51 +02:00
values . put ( PROFILE_SHARING , contact . isProfileSharingEnabled ( ) ? "1" : "0" ) ;
values . put ( BLOCKED , contact . isBlocked ( ) ? "1" : "0" ) ;
2020-02-28 19:03:06 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( contact . getId ( ) . getRaw ( ) ) ) ;
2019-09-26 16:12:51 +02:00
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
return values ;
}
2020-02-10 19:42:43 +01:00
private static @NonNull ContentValues getValuesForStorageGroupV1 ( @NonNull SignalGroupV1Record groupV1 ) {
ContentValues values = new ContentValues ( ) ;
2020-03-26 15:00:17 +01:00
values . put ( GROUP_ID , GroupId . v1 ( groupV1 . getGroupId ( ) ) . toString ( ) ) ;
2020-02-10 19:42:43 +01:00
values . put ( GROUP_TYPE , GroupType . SIGNAL_V1 . getId ( ) ) ;
values . put ( PROFILE_SHARING , groupV1 . isProfileSharingEnabled ( ) ? "1" : "0" ) ;
values . put ( BLOCKED , groupV1 . isBlocked ( ) ? "1" : "0" ) ;
2020-02-28 19:03:06 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( groupV1 . getId ( ) . getRaw ( ) ) ) ;
2020-02-10 19:42:43 +01:00
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
return values ;
}
2019-09-26 16:12:51 +02:00
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 ;
}
/ * *
2020-03-18 21:31:45 +01:00
* @return All storage ids for ContactRecords , excluding the ones that need to be deleted .
2019-09-26 16:12:51 +02:00
* /
2020-03-18 21:31:45 +01:00
public List < StorageId > getContactStorageSyncIds ( ) {
return new ArrayList < > ( getContactStorageSyncIdsMap ( ) . values ( ) ) ;
2019-09-26 16:12:51 +02:00
}
/ * *
2020-03-18 21:31:45 +01:00
* @return All storage IDs for ContactRecords , excluding the ones that need to be deleted .
2019-09-26 16:12:51 +02:00
* /
2020-03-18 21:31:45 +01:00
public @NonNull Map < RecipientId , StorageId > getContactStorageSyncIdsMap ( ) {
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
String query = STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " != ? AND " + ID + " != ?" ;
String [ ] args = new String [ ] { String . valueOf ( DirtyState . DELETE ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-28 19:03:06 +01:00
Map < RecipientId , StorageId > out = new HashMap < > ( ) ;
2019-09-26 16:12:51 +02:00
2020-02-28 19:03:06 +01:00
try ( Cursor cursor = db . query ( TABLE_NAME , new String [ ] { ID , STORAGE_SERVICE_ID , GROUP_TYPE } , query , args , null , null , null ) ) {
2019-09-26 16:12:51 +02:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2020-02-10 19:42:43 +01:00
RecipientId id = RecipientId . from ( cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ) ;
2020-02-28 19:03:06 +01:00
String encodedKey = cursor . getString ( cursor . getColumnIndexOrThrow ( STORAGE_SERVICE_ID ) ) ;
GroupType groupType = GroupType . fromId ( cursor . getInt ( cursor . getColumnIndexOrThrow ( GROUP_TYPE ) ) ) ;
byte [ ] key = Base64 . decodeOrThrow ( encodedKey ) ;
2019-09-26 16:12:51 +02:00
2020-02-28 19:03:06 +01:00
if ( groupType = = GroupType . NONE ) {
out . put ( id , StorageId . forContact ( key ) ) ;
} else {
out . put ( id , StorageId . forGroupV1 ( key ) ) ;
}
2019-09-26 16:12:51 +02:00
}
}
return out ;
}
2020-03-26 15:00:17 +01:00
private static @NonNull RecipientSettings getRecipientSettings ( @NonNull Cursor cursor ) {
2020-02-11 00:40:22 +01:00
long id = cursor . getLong ( cursor . getColumnIndexOrThrow ( ID ) ) ;
UUID uuid = UuidUtil . parseOrNull ( cursor . getString ( cursor . getColumnIndexOrThrow ( UUID ) ) ) ;
String username = cursor . getString ( cursor . getColumnIndexOrThrow ( USERNAME ) ) ;
String e164 = cursor . getString ( cursor . getColumnIndexOrThrow ( PHONE ) ) ;
String email = cursor . getString ( cursor . getColumnIndexOrThrow ( EMAIL ) ) ;
2020-03-26 15:00:17 +01:00
GroupId groupId = GroupId . parseNullable ( cursor . getString ( cursor . getColumnIndexOrThrow ( GROUP_ID ) ) ) ;
2020-02-10 19:42:43 +01:00
int groupType = cursor . getInt ( cursor . getColumnIndexOrThrow ( GROUP_TYPE ) ) ;
2020-02-11 00:40:22 +01:00
boolean blocked = cursor . getInt ( cursor . getColumnIndexOrThrow ( BLOCKED ) ) = = 1 ;
String messageRingtone = cursor . getString ( cursor . getColumnIndexOrThrow ( MESSAGE_RINGTONE ) ) ;
String callRingtone = cursor . getString ( cursor . getColumnIndexOrThrow ( CALL_RINGTONE ) ) ;
int messageVibrateState = cursor . getInt ( cursor . getColumnIndexOrThrow ( MESSAGE_VIBRATE ) ) ;
int callVibrateState = cursor . getInt ( cursor . getColumnIndexOrThrow ( CALL_VIBRATE ) ) ;
long muteUntil = cursor . getLong ( cursor . getColumnIndexOrThrow ( MUTE_UNTIL ) ) ;
String serializedColor = cursor . getString ( cursor . getColumnIndexOrThrow ( COLOR ) ) ;
int insightsBannerTier = cursor . getInt ( cursor . getColumnIndexOrThrow ( SEEN_INVITE_REMINDER ) ) ;
int defaultSubscriptionId = cursor . getInt ( cursor . getColumnIndexOrThrow ( DEFAULT_SUBSCRIPTION_ID ) ) ;
int expireMessages = cursor . getInt ( cursor . getColumnIndexOrThrow ( MESSAGE_EXPIRATION_TIME ) ) ;
int registeredState = cursor . getInt ( cursor . getColumnIndexOrThrow ( REGISTERED ) ) ;
String profileKeyString = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_KEY ) ) ;
String profileKeyCredentialString = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_KEY_CREDENTIAL ) ) ;
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 ) ) ;
String profileGivenName = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_GIVEN_NAME ) ) ;
String profileFamilyName = cursor . getString ( cursor . getColumnIndexOrThrow ( PROFILE_FAMILY_NAME ) ) ;
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 ) ) ;
boolean forceSmsSelection = cursor . getInt ( cursor . getColumnIndexOrThrow ( FORCE_SMS_SELECTION ) ) = = 1 ;
2020-02-14 21:47:31 +01:00
int uuidCapabilityValue = cursor . getInt ( cursor . getColumnIndexOrThrow ( UUID_CAPABILITY ) ) ;
int groupsV2CapabilityValue = cursor . getInt ( cursor . getColumnIndexOrThrow ( GROUPS_V2_CAPABILITY ) ) ;
2020-02-28 19:03:06 +01:00
String storageKeyRaw = cursor . getString ( cursor . getColumnIndexOrThrow ( STORAGE_SERVICE_ID ) ) ;
2020-02-11 00:40:22 +01:00
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 ;
2020-02-11 00:40:22 +01:00
byte [ ] profileKey = null ;
byte [ ] profileKeyCredential = 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 ;
}
2020-02-11 00:40:22 +01:00
if ( profileKeyCredentialString ! = null ) {
try {
profileKeyCredential = Base64 . decode ( profileKeyCredentialString ) ;
} catch ( IOException e ) {
Log . w ( TAG , e ) ;
profileKeyCredential = null ;
}
}
2017-08-15 03:11:13 +02:00
}
2020-02-10 19:42:43 +01:00
byte [ ] storageKey = storageKeyRaw ! = null ? Base64 . decodeOrThrow ( storageKeyRaw ) : null ;
byte [ ] identityKey = identityKeyRaw ! = null ? Base64 . decodeOrThrow ( identityKeyRaw ) : null ;
2019-09-26 16:12:51 +02:00
IdentityDatabase . VerifiedStatus identityStatus = IdentityDatabase . VerifiedStatus . forState ( identityStatusRaw ) ;
2020-02-10 19:42:43 +01:00
return new RecipientSettings ( RecipientId . from ( id ) , uuid , username , e164 , email , groupId , GroupType . fromId ( groupType ) , 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 ) ,
2020-02-11 00:40:22 +01:00
profileKey , profileKeyCredential ,
systemDisplayName , systemContactPhoto ,
2019-08-07 20:22:51 +02:00
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 ) ,
2020-02-14 17:00:32 +01:00
forceSmsSelection ,
2020-02-14 21:47:31 +01:00
Recipient . Capability . deserialize ( uuidCapabilityValue ) ,
Recipient . Capability . deserialize ( groupsV2CapabilityValue ) ,
2020-02-14 17:00:32 +01:00
InsightsBannerTier . fromId ( insightsBannerTier ) ,
2019-09-26 16:12:51 +02:00
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
}
2020-02-14 17:00:32 +01:00
public void setCapabilities ( @NonNull RecipientId id , @NonNull SignalServiceProfile . Capabilities capabilities ) {
ContentValues values = new ContentValues ( 2 ) ;
2020-02-14 21:47:31 +01:00
values . put ( UUID_CAPABILITY , Recipient . Capability . fromBoolean ( capabilities . isUuid ( ) ) . serialize ( ) ) ;
2020-02-14 17:00:32 +01:00
values . put ( GROUPS_V2_CAPABILITY , Recipient . Capability . fromBoolean ( capabilities . isGv2 ( ) ) . serialize ( ) ) ;
2019-09-07 05:40:06 +02:00
update ( id , values ) ;
Recipient . live ( id ) . refresh ( ) ;
}
2020-02-11 00:40:22 +01:00
/ * *
* Updates the profile key .
* < p >
2020-02-12 22:30:20 +01:00
* If it changes , it clears out the profile key credential and resets the unidentified access mode .
* @return true iff changed .
2020-02-11 00:40:22 +01:00
* /
2020-02-12 22:30:20 +01:00
public boolean setProfileKey ( @NonNull RecipientId id , @NonNull ProfileKey profileKey ) {
2020-02-11 00:40:22 +01:00
String selection = ID + " = ?" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
ContentValues valuesToCompare = new ContentValues ( 1 ) ;
2020-02-12 22:30:20 +01:00
ContentValues valuesToSet = new ContentValues ( 3 ) ;
String encodedProfileKey = Base64 . encodeBytes ( profileKey . serialize ( ) ) ;
2020-02-11 00:40:22 +01:00
valuesToCompare . put ( PROFILE_KEY , encodedProfileKey ) ;
valuesToSet . put ( PROFILE_KEY , encodedProfileKey ) ;
valuesToSet . putNull ( PROFILE_KEY_CREDENTIAL ) ;
2020-02-12 22:30:20 +01:00
valuesToSet . put ( UNIDENTIFIED_ACCESS_MODE , UnidentifiedAccessMode . UNKNOWN . getMode ( ) ) ;
2020-02-11 00:40:22 +01:00
SqlUtil . UpdateQuery updateQuery = SqlUtil . buildTrueUpdateQuery ( selection , args , valuesToCompare ) ;
if ( update ( updateQuery , valuesToSet ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
2020-03-18 21:31:45 +01:00
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
2020-02-12 22:30:20 +01:00
return true ;
} else {
return false ;
2020-02-11 00:40:22 +01:00
}
}
/ * *
* Updates the profile key credential as long as the profile key matches .
* /
public void setProfileKeyCredential ( @NonNull RecipientId id ,
@NonNull ProfileKey profileKey ,
@NonNull ProfileKeyCredential profileKeyCredential )
{
String selection = ID + " = ? AND " + PROFILE_KEY + " = ?" ;
String [ ] args = new String [ ] { id . serialize ( ) , Base64 . encodeBytes ( profileKey . serialize ( ) ) } ;
ContentValues values = new ContentValues ( 1 ) ;
values . put ( PROFILE_KEY_CREDENTIAL , Base64 . encodeBytes ( profileKeyCredential . serialize ( ) ) ) ;
SqlUtil . UpdateQuery updateQuery = SqlUtil . buildTrueUpdateQuery ( selection , args , values ) ;
if ( update ( updateQuery , values ) ) {
// TODO [greyson] If we sync this in future, mark dirty
//markDirty(id, DirtyState.UPDATE);
Recipient . live ( id ) . refresh ( ) ;
}
}
private void clearProfileKeyCredential ( @NonNull RecipientId id ) {
2017-08-15 03:11:13 +02:00
ContentValues values = new ContentValues ( 1 ) ;
2020-02-11 00:40:22 +01:00
values . putNull ( PROFILE_KEY_CREDENTIAL ) ;
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 ( ) ;
2020-03-18 21:31:45 +01:00
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
2019-09-26 16:12:51 +02:00
}
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 ( ) ;
2020-03-18 21:31:45 +01:00
if ( id . equals ( Recipient . self ( ) . getId ( ) ) ) {
markDirty ( id , DirtyState . UPDATE ) ;
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
}
2019-09-26 16:12:51 +02:00
}
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 ( ) ;
2020-03-18 21:31:45 +01:00
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
2019-09-26 16:12:51 +02:00
}
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 ( ) ;
2020-03-18 21:31:45 +01:00
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
2019-09-26 16:12:51 +02:00
}
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 ) ;
2020-03-18 21:31:45 +01:00
if ( update ( id , contentValues ) ) {
Recipient . live ( id ) . refresh ( ) ;
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
}
2019-10-29 01:16:11 +01:00
}
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
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
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 ) {
2020-02-10 19:42:43 +01:00
ContentValues contentValues = new ContentValues ( 2 ) ;
2017-08-22 19:44:04 +02:00
contentValues . put ( REGISTERED , registeredState . getId ( ) ) ;
2020-02-10 19:42:43 +01:00
if ( registeredState = = RegisteredState . REGISTERED ) {
2020-02-28 19:03:06 +01:00
contentValues . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
2020-02-10 19:42:43 +01:00
}
if ( update ( id , contentValues ) ) {
if ( registeredState = = RegisteredState . REGISTERED ) {
markDirty ( id , DirtyState . INSERT ) ;
} else if ( registeredState = = RegisteredState . NOT_REGISTERED ) {
markDirty ( id , DirtyState . DELETE ) ;
}
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 ) {
2020-02-10 19:42:43 +01:00
ContentValues registeredValues = new ContentValues ( 1 ) ;
registeredValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
2017-08-07 23:24:53 +02:00
2020-02-10 19:42:43 +01:00
if ( update ( activeId , registeredValues ) ) {
markDirty ( activeId , DirtyState . INSERT ) ;
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 ) ) {
2020-02-10 19:42:43 +01:00
markDirty ( inactiveId , DirtyState . DELETE ) ;
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 " +
2019-12-20 21:12:22 +01:00
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + SEARCH_PROFILE_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)" ;
2020-03-16 19:33:57 +01:00
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) } ;
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 " +
"(" +
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
")" ;
2020-03-16 19:33:57 +01:00
String [ ] args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , 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 ) ;
2020-02-21 19:52:27 +01:00
setBlocked . put ( PROFILE_SHARING , 0 ) ;
2019-11-08 21:33:10 +01:00
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 } ) ;
}
2020-03-27 15:28:48 +01:00
List < GroupId . V1 > groupIdStrings = Stream . of ( groupIds ) . map ( GroupId : : v1 ) . toList ( ) ;
2019-11-08 21:33:10 +01:00
2020-03-27 15:28:48 +01:00
for ( GroupId . V1 groupId : groupIdStrings ) {
2020-03-26 15:00:17 +01:00
db . update ( TABLE_NAME , setBlocked , GROUP_ID + " = ?" , new String [ ] { groupId . toString ( ) } ) ;
2019-11-08 21:33:10 +01:00
}
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 ( ) ;
2020-02-28 19:03:06 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( entry . getValue ( ) ) ) ;
2019-09-26 16:12:51 +02:00
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 ) {
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( DIRTY , dirtyState . getId ( ) ) ;
2020-02-10 19:42:43 +01:00
String query = ID + " = ? AND (" + UUID + " NOT NULL OR " + PHONE + " NOT NULL) AND " ;
2019-09-26 16:12:51 +02:00
String [ ] args = new String [ ] { recipientId . serialize ( ) , String . valueOf ( dirtyState . id ) } ;
2020-02-10 19:42:43 +01:00
switch ( dirtyState ) {
case INSERT :
query + = "(" + DIRTY + " < ? OR " + DIRTY + " = ?)" ;
args = SqlUtil . appendArg ( args , String . valueOf ( DirtyState . DELETE . getId ( ) ) ) ;
2020-02-28 19:03:06 +01:00
contentValues . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
2020-02-10 19:42:43 +01:00
break ;
case DELETE :
query + = "(" + DIRTY + " < ? OR " + DIRTY + " = ?)" ;
args = SqlUtil . appendArg ( args , String . valueOf ( DirtyState . INSERT . getId ( ) ) ) ;
break ;
default :
query + = DIRTY + " < ?" ;
}
2019-09-26 16:12:51 +02:00
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 .
* /
2020-02-11 00:40:22 +01:00
private boolean update ( @NonNull RecipientId id , @NonNull ContentValues contentValues ) {
String selection = ID + " = ?" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
SqlUtil . UpdateQuery updateQuery = SqlUtil . buildTrueUpdateQuery ( selection , args , contentValues ) ;
2019-09-26 16:12:51 +02:00
2020-02-11 00:40:22 +01:00
return update ( updateQuery , contentValues ) ;
}
2019-09-26 16:12:51 +02:00
2020-02-11 00:40:22 +01:00
/ * *
* Will update the database with the { @param contentValues } you specified .
* < p >
* This will only return true if a row was * actually * updated with respect to the where clause of the { @param updateQuery } .
* /
private boolean update ( @NonNull SqlUtil . UpdateQuery updateQuery , @NonNull ContentValues contentValues ) {
SQLiteDatabase database = databaseHelper . getWritableDatabase ( ) ;
return database . update ( TABLE_NAME , contentValues , updateQuery . getWhere ( ) , updateQuery . getWhereArgs ( ) ) > 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 ( ) ;
}
}
}
2020-02-10 19:42:43 +01:00
private @NonNull GetOrInsertResult getOrInsertByColumn ( @NonNull String column , String value ) {
2019-10-07 22:36:05 +02:00
if ( TextUtils . isEmpty ( value ) ) {
throw new AssertionError ( column + " cannot be empty." ) ;
}
Optional < RecipientId > existing = getByColumn ( column , value ) ;
if ( existing . isPresent ( ) ) {
2020-02-10 19:42:43 +01:00
return new GetOrInsertResult ( existing . get ( ) , false ) ;
2019-10-07 22:36:05 +02:00
} 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 ( ) ) {
2020-02-10 19:42:43 +01:00
return new GetOrInsertResult ( existing . get ( ) , false ) ;
2019-10-07 22:36:05 +02:00
} else {
throw new AssertionError ( "Failed to insert recipient!" ) ;
}
2019-10-09 04:14:52 +02:00
} else {
2020-02-10 19:42:43 +01:00
return new GetOrInsertResult ( RecipientId . from ( id ) , true ) ;
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 ( ) {
2020-02-28 19:03:06 +01:00
String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " < ?" ;
2019-09-26 16:12:51 +02:00
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
}
2020-03-16 17:41:48 +01:00
private static @NonNull String nullIfEmpty ( String column ) {
return "NULLIF(" + column + ", '')" ;
}
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 ;
2020-03-26 15:00:17 +01:00
private final GroupId groupId ;
2020-02-10 19:42:43 +01:00
private final GroupType groupType ;
2019-09-26 16:12:51 +02:00
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 ;
2020-02-11 00:40:22 +01:00
private final byte [ ] profileKeyCredential ;
2019-09-26 16:12:51 +02:00
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 ;
2020-02-14 21:47:31 +01:00
private final Recipient . Capability uuidCapability ;
2020-02-14 17:00:32 +01:00
private final Recipient . Capability groupsV2Capability ;
2019-09-26 16:12:51 +02:00
private final InsightsBannerTier insightsBannerTier ;
2020-03-18 21:31:45 +01:00
private final byte [ ] storageId ;
2019-09-26 16:12:51 +02:00
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 ,
2020-03-26 15:00:17 +01:00
@Nullable GroupId groupId ,
2020-02-10 19:42:43 +01:00
@NonNull GroupType groupType ,
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 ,
2020-02-11 00:40:22 +01:00
@Nullable byte [ ] profileKeyCredential ,
2017-08-22 03:47:37 +02:00
@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 ,
2020-02-14 21:47:31 +01:00
Recipient . Capability uuidCapability ,
2020-02-14 17:00:32 +01:00
Recipient . Capability groupsV2Capability ,
2019-09-26 16:12:51 +02:00
@NonNull InsightsBannerTier insightsBannerTier ,
2020-03-18 21:31:45 +01:00
@Nullable byte [ ] storageId ,
2019-09-26 16:12:51 +02:00
@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 ;
2020-02-10 19:42:43 +01:00
this . groupType = groupType ;
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 ;
2020-02-11 00:40:22 +01:00
this . profileKeyCredential = profileKeyCredential ;
2018-05-22 11:13:10 +02:00
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 ;
2020-02-14 21:47:31 +01:00
this . uuidCapability = uuidCapability ;
2020-02-14 17:00:32 +01:00
this . groupsV2Capability = groupsV2Capability ;
2019-09-26 16:12:51 +02:00
this . insightsBannerTier = insightsBannerTier ;
2020-03-18 21:31:45 +01:00
this . storageId = storageId ;
2019-09-26 16:12:51 +02:00
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 ;
}
2020-03-26 15:00:17 +01:00
public @Nullable GroupId getGroupId ( ) {
2019-09-07 05:40:06 +02:00
return groupId ;
2019-08-07 20:22:51 +02:00
}
2020-02-10 19:42:43 +01:00
public @NonNull GroupType getGroupType ( ) {
return groupType ;
}
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 ;
}
2020-02-11 00:40:22 +01:00
public @Nullable byte [ ] getProfileKeyCredential ( ) {
return profileKeyCredential ;
}
2017-08-15 03:11:13 +02:00
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
2020-02-14 21:47:31 +01:00
public Recipient . Capability getUuidCapability ( ) {
return uuidCapability ;
2019-09-07 05:40:06 +02:00
}
2019-09-26 16:12:51 +02:00
2020-02-14 17:00:32 +01:00
public Recipient . Capability getGroupsV2Capability ( ) {
return groupsV2Capability ;
}
2020-03-18 21:31:45 +01:00
public @Nullable byte [ ] getStorageId ( ) {
return storageId ;
2019-09-26 16:12:51 +02:00
}
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 ) ;
}
}
2020-02-10 19:42:43 +01:00
private static class GetOrInsertResult {
final RecipientId recipientId ;
final boolean neededInsert ;
private GetOrInsertResult ( @NonNull RecipientId recipientId , boolean neededInsert ) {
this . recipientId = recipientId ;
this . neededInsert = neededInsert ;
}
}
2015-06-09 16:37:20 +02:00
}