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 ;
2020-07-16 00:03:18 +02:00
import net.sqlcipher.database.SQLiteConstraintException ;
2018-01-25 04:17:44 +01:00
import net.sqlcipher.database.SQLiteDatabase ;
2020-09-09 16:59:09 +02:00
import org.signal.storageservice.protos.groups.local.DecryptedGroup ;
2020-05-19 23:48:26 +02:00
import org.signal.zkgroup.groups.GroupMasterKey ;
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 ;
2020-06-10 16:49:22 +02:00
import org.thoughtcrime.securesms.contacts.avatars.ContactColors ;
2020-05-02 00:13:23 +02:00
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil ;
2019-09-26 16:12:51 +02:00
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord ;
2020-10-06 16:24:14 +02:00
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus ;
2018-01-25 04:17:44 +01:00
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper ;
2020-06-12 19:22:46 +02:00
import org.thoughtcrime.securesms.database.model.ThreadRecord ;
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 ;
2020-05-02 00:13:23 +02:00
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet ;
2020-09-09 16:59:09 +02:00
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor ;
2020-07-16 15:33:47 +02:00
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob ;
2020-09-09 16:59:09 +02:00
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob ;
2018-05-22 11:13:10 +02:00
import org.thoughtcrime.securesms.logging.Log ;
2020-04-22 16:13:56 +02:00
import org.thoughtcrime.securesms.profiles.AvatarHelper ;
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 ;
2020-07-02 17:19:52 +02:00
import org.thoughtcrime.securesms.util.CursorUtil ;
2020-10-06 16:24:14 +02:00
import org.thoughtcrime.securesms.util.GroupUtil ;
2019-09-26 16:12:51 +02:00
import org.thoughtcrime.securesms.util.IdentityUtil ;
2019-12-05 17:41:47 +01:00
import org.thoughtcrime.securesms.util.SqlUtil ;
2020-07-23 04:36:10 +02:00
import org.thoughtcrime.securesms.util.StringUtil ;
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 ;
2020-07-16 00:03:18 +02:00
import org.whispersystems.libsignal.util.Pair ;
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-05-19 23:48:26 +02:00
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record ;
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 ;
2020-10-06 16:24:14 +02:00
import java.util.Arrays ;
2019-09-07 05:40:06 +02:00
import java.util.Collection ;
2020-05-02 00:13:23 +02:00
import java.util.Collections ;
2017-11-26 19:45:39 +01:00
import java.util.HashMap ;
2017-08-07 23:24:53 +02:00
import java.util.HashSet ;
2020-06-12 19:22:46 +02:00
import java.util.LinkedHashSet ;
2017-08-07 23:24:53 +02:00
import java.util.LinkedList ;
2017-08-07 06:43:11 +02:00
import java.util.List ;
2020-05-02 00:13:23 +02:00
import java.util.Locale ;
2017-11-26 19:45:39 +01:00
import java.util.Map ;
2020-07-16 00:03:18 +02:00
import java.util.Objects ;
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" ;
2020-06-12 19:22:46 +02:00
private static final String LAST_PROFILE_FETCH = "last_profile_fetch" ;
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" ;
2020-08-05 22:45:52 +02:00
private static final String MENTION_SETTING = "mention_setting" ;
2020-08-20 22:05:50 +02:00
private static final String STORAGE_PROTO = "storage_proto" ;
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" ;
2017-08-07 06:43:11 +02:00
private static final String [ ] RECIPIENT_PROJECTION = new String [ ] {
2020-10-06 16:24:14 +02:00
ID , 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 ,
2020-06-12 19:22:46 +02:00
PROFILE_GIVEN_NAME , PROFILE_FAMILY_NAME , SIGNAL_PROFILE_AVATAR , PROFILE_SHARING , LAST_PROFILE_FETCH ,
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-08-05 22:45:52 +02:00
STORAGE_SERVICE_ID , DIRTY ,
MENTION_SETTING
2019-09-26 16:12:51 +02:00
} ;
2020-05-19 23:48:26 +02:00
private static final String [ ] ID_PROJECTION = new String [ ] { ID } ;
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 } ;
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 } ;
2020-10-06 16:24:14 +02:00
private static final String [ ] TYPED_RECIPIENT_PROJECTION = Stream . of ( RECIPIENT_PROJECTION )
2020-05-19 23:48:26 +02:00
. map ( columnName - > TABLE_NAME + "." + columnName )
. toList ( ) . toArray ( new String [ 0 ] ) ;
2020-10-06 16:24:14 +02:00
static final String [ ] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays . copyOfRange ( TYPED_RECIPIENT_PROJECTION , 1 , TYPED_RECIPIENT_PROJECTION . length ) ;
2020-08-05 22:45:52 +02:00
2020-10-06 16:24:14 +02:00
private static final String [ ] MENTION_SEARCH_PROJECTION = new String [ ] { ID , removeWhitespace ( "COALESCE(" + nullIfEmpty ( SYSTEM_DISPLAY_NAME ) + ", " + nullIfEmpty ( PROFILE_JOINED_NAME ) + ", " + nullIfEmpty ( PROFILE_GIVEN_NAME ) + ", " + nullIfEmpty ( USERNAME ) + ", " + nullIfEmpty ( PHONE ) + ")" ) + " AS " + SORT_NAME } ;
2019-09-26 16:12:51 +02:00
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
} ;
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 ] ;
}
2020-06-19 18:08:54 +02:00
public static VibrateState fromBoolean ( boolean enabled ) {
return enabled ? ENABLED : DISABLED ;
}
2015-06-09 16:37:20 +02:00
}
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 {
2020-05-19 23:48:26 +02:00
NONE ( 0 ) , MMS ( 1 ) , SIGNAL_V1 ( 2 ) , SIGNAL_V2 ( 3 ) ;
2020-02-10 19:42:43 +01:00
private final int id ;
GroupType ( int id ) {
this . id = id ;
}
int getId ( ) {
return id ;
}
public static GroupType fromId ( int id ) {
return values ( ) [ id ] ;
}
}
2020-08-05 22:45:52 +02:00
public enum MentionSetting {
2020-08-13 15:54:33 +02:00
ALWAYS_NOTIFY ( 0 ) , DO_NOT_NOTIFY ( 1 ) ;
2020-08-05 22:45:52 +02:00
private final int id ;
MentionSetting ( int id ) {
this . id = id ;
}
int getId ( ) {
return id ;
}
public static MentionSetting 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, " +
2020-06-12 19:22:46 +02:00
LAST_PROFILE_FETCH + " INTEGER DEFAULT 0, " +
2019-08-07 20:22:51 +02:00
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-08-05 22:45:52 +02:00
DIRTY + " INTEGER DEFAULT " + DirtyState . CLEAN . getId ( ) + ", " +
2020-08-24 19:39:28 +02:00
MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting . ALWAYS_NOTIFY . getId ( ) + ", " +
2020-08-20 22:05:50 +02:00
STORAGE_PROTO + " TEXT DEFAULT NULL);" ;
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 ) ;
}
2020-07-16 00:03:18 +02:00
public @NonNull RecipientId getAndPossiblyMerge ( @Nullable UUID uuid , @Nullable String e164 , boolean highTrust ) {
if ( uuid = = null & & e164 = = null ) {
throw new IllegalArgumentException ( "Must provide a UUID or E164!" ) ;
}
RecipientId recipientNeedingRefresh = null ;
Pair < RecipientId , RecipientId > remapped = null ;
boolean transactionSuccessful = false ;
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
Optional < RecipientId > byE164 = e164 ! = null ? getByE164 ( e164 ) : Optional . absent ( ) ;
Optional < RecipientId > byUuid = uuid ! = null ? getByUuid ( uuid ) : Optional . absent ( ) ;
RecipientId finalId ;
if ( ! byE164 . isPresent ( ) & & ! byUuid . isPresent ( ) ) {
Log . i ( TAG , "Discovered a completely new user. Inserting." ) ;
if ( highTrust ) {
long id = db . insert ( TABLE_NAME , null , buildContentValuesForNewUser ( e164 , uuid ) ) ;
finalId = RecipientId . from ( id ) ;
} else {
long id = db . insert ( TABLE_NAME , null , buildContentValuesForNewUser ( uuid = = null ? e164 : null , uuid ) ) ;
finalId = RecipientId . from ( id ) ;
}
} else if ( byE164 . isPresent ( ) & & ! byUuid . isPresent ( ) ) {
if ( uuid ! = null ) {
RecipientSettings e164Settings = getRecipientSettings ( byE164 . get ( ) ) ;
if ( e164Settings . uuid ! = null ) {
if ( highTrust ) {
Log . w ( TAG , "Found out about a UUID for a known E164 user, but that user already has a UUID. Likely a case of re-registration. High-trust, so stripping the E164 from the existing account and assigning it to a new entry." ) ;
removePhoneNumber ( byE164 . get ( ) , db ) ;
recipientNeedingRefresh = byE164 . get ( ) ;
ContentValues insertValues = buildContentValuesForNewUser ( e164 , uuid ) ;
insertValues . put ( BLOCKED , e164Settings . blocked ? 1 : 0 ) ;
long id = db . insert ( TABLE_NAME , null , insertValues ) ;
finalId = RecipientId . from ( id ) ;
} else {
Log . w ( TAG , "Found out about a UUID for a known E164 user, but that user already has a UUID. Likely a case of re-registration. Low-trust, so making a new user for the UUID." ) ;
long id = db . insert ( TABLE_NAME , null , buildContentValuesForNewUser ( null , uuid ) ) ;
finalId = RecipientId . from ( id ) ;
}
} else {
if ( highTrust ) {
Log . i ( TAG , "Found out about a UUID for a known E164 user. High-trust, so updating." ) ;
markRegisteredOrThrow ( byE164 . get ( ) , uuid ) ;
finalId = byE164 . get ( ) ;
} else {
Log . i ( TAG , "Found out about a UUID for a known E164 user. Low-trust, so making a new user for the UUID." ) ;
long id = db . insert ( TABLE_NAME , null , buildContentValuesForNewUser ( null , uuid ) ) ;
finalId = RecipientId . from ( id ) ;
}
}
} else {
finalId = byE164 . get ( ) ;
}
} else if ( ! byE164 . isPresent ( ) & & byUuid . isPresent ( ) ) {
if ( e164 ! = null ) {
if ( highTrust ) {
Log . i ( TAG , "Found out about an E164 for a known UUID user. High-trust, so updating." ) ;
setPhoneNumberOrThrow ( byUuid . get ( ) , e164 ) ;
finalId = byUuid . get ( ) ;
} else {
Log . i ( TAG , "Found out about an E164 for a known UUID user. Low-trust, so doing nothing." ) ;
finalId = byUuid . get ( ) ;
}
} else {
finalId = byUuid . get ( ) ;
}
} else {
if ( byE164 . equals ( byUuid ) ) {
finalId = byUuid . get ( ) ;
} else {
Log . w ( TAG , "Hit a conflict between " + byE164 . get ( ) + " (E164) and " + byUuid . get ( ) + " (UUID). They map to different recipients." , new Throwable ( ) ) ;
RecipientSettings e164Settings = getRecipientSettings ( byE164 . get ( ) ) ;
if ( e164Settings . getUuid ( ) ! = null ) {
if ( highTrust ) {
Log . w ( TAG , "The E164 contact has a different UUID. Likely a case of re-registration. High-trust, so stripping the E164 from the existing account and assigning it to the UUID entry." ) ;
removePhoneNumber ( byE164 . get ( ) , db ) ;
recipientNeedingRefresh = byE164 . get ( ) ;
setPhoneNumberOrThrow ( byUuid . get ( ) , Objects . requireNonNull ( e164 ) ) ;
finalId = byUuid . get ( ) ;
} else {
Log . w ( TAG , "The E164 contact has a different UUID. Likely a case of re-registration. Low-trust, so doing nothing." ) ;
finalId = byUuid . get ( ) ;
}
} else {
if ( highTrust ) {
Log . w ( TAG , "We have one contact with just an E164, and another with UUID. High-trust, so merging the two rows together." ) ;
finalId = merge ( byUuid . get ( ) , byE164 . get ( ) ) ;
recipientNeedingRefresh = byUuid . get ( ) ;
remapped = new Pair < > ( byE164 . get ( ) , byUuid . get ( ) ) ;
} else {
Log . w ( TAG , "We have one contact with just an E164, and another with UUID. Low-trust, so doing nothing." ) ;
finalId = byUuid . get ( ) ;
}
}
}
}
db . setTransactionSuccessful ( ) ;
transactionSuccessful = true ;
return finalId ;
} finally {
db . endTransaction ( ) ;
if ( transactionSuccessful ) {
if ( recipientNeedingRefresh ! = null ) {
Recipient . live ( recipientNeedingRefresh ) . refresh ( ) ;
}
if ( remapped ! = null ) {
Recipient . live ( remapped . first ( ) ) . refresh ( remapped . second ( ) ) ;
}
if ( recipientNeedingRefresh ! = null | | remapped ! = null ) {
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
RecipientId . clearCache ( ) ;
}
}
}
}
private static ContentValues buildContentValuesForNewUser ( @Nullable String e164 , @Nullable UUID uuid ) {
ContentValues values = new ContentValues ( ) ;
values . put ( PHONE , e164 ) ;
if ( uuid ! = null ) {
values . put ( UUID , uuid . toString ( ) . toLowerCase ( ) ) ;
values . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
values . put ( DIRTY , DirtyState . INSERT . getId ( ) ) ;
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( StorageSyncHelper . generateKey ( ) ) ) ;
}
return values ;
}
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 {
2020-05-19 23:48:26 +02:00
if ( groupId . isV2 ( ) ) {
values . put ( GROUP_TYPE , GroupType . SIGNAL_V2 . getId ( ) ) ;
} else {
values . put ( GROUP_TYPE , GroupType . SIGNAL_V1 . getId ( ) ) ;
}
2020-02-10 19:42:43 +01:00
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 ( ) ;
2020-10-06 16:24:14 +02:00
String query = ID + " = ?" ;
2019-08-07 20:22:51 +02:00
String [ ] args = new String [ ] { id . serialize ( ) } ;
2015-06-09 16:37:20 +02:00
2020-10-06 16:24:14 +02:00
try ( Cursor cursor = database . query ( TABLE_NAME , RECIPIENT_PROJECTION , query , args , null , null , null ) ) {
2015-06-09 16:37:20 +02:00
if ( cursor ! = null & & cursor . moveToNext ( ) ) {
2020-04-22 16:13:56 +02:00
return getRecipientSettings ( context , cursor ) ;
2019-08-07 20:22:51 +02:00
} else {
2020-07-16 00:03:18 +02:00
Optional < RecipientId > remapped = RemappedRecords . getInstance ( ) . getRecipient ( context , id ) ;
if ( remapped . isPresent ( ) ) {
Log . w ( TAG , "Missing recipient, but found it in the remapped records." ) ;
return getRecipientSettings ( remapped . get ( ) ) ;
} else {
throw new MissingRecipientException ( 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 ;
}
2020-05-19 23:48:26 +02:00
public @Nullable RecipientSettings getRecipientSettingsForSync ( @NonNull RecipientId id ) {
String query = TABLE_NAME + "." + ID + " = ?" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
List < RecipientSettings > recipientSettingsForSync = getRecipientSettingsForSync ( query , args ) ;
if ( recipientSettingsForSync . isEmpty ( ) ) {
return null ;
}
if ( recipientSettingsForSync . size ( ) > 1 ) {
throw new AssertionError ( ) ;
}
return recipientSettingsForSync . get ( 0 ) ;
}
2019-09-26 16:12:51 +02:00
public @NonNull List < RecipientSettings > getPendingRecipientSyncUpdates ( ) {
2020-05-19 23:48:26 +02:00
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
2020-03-18 21:31:45 +01:00
String [ ] args = new String [ ] { String . valueOf ( DirtyState . UPDATE . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
2020-05-19 23:48:26 +02:00
return getRecipientSettingsForSync ( query , args ) ;
2019-09-26 16:12:51 +02:00
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncInsertions ( ) {
2020-05-19 23:48:26 +02:00
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
2020-03-18 21:31:45 +01:00
String [ ] args = new String [ ] { String . valueOf ( DirtyState . INSERT . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
2020-05-19 23:48:26 +02:00
return getRecipientSettingsForSync ( query , args ) ;
2019-09-26 16:12:51 +02:00
}
public @NonNull List < RecipientSettings > getPendingRecipientSyncDeletions ( ) {
2020-05-19 23:48:26 +02:00
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?" ;
2020-03-18 21:31:45 +01:00
String [ ] args = new String [ ] { String . valueOf ( DirtyState . DELETE . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-02-10 19:42:43 +01:00
2020-05-19 23:48:26 +02:00
return getRecipientSettingsForSync ( 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 ) {
2020-05-19 23:48:26 +02:00
List < RecipientSettings > result = getRecipientSettingsForSync ( TABLE_NAME + "." + 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-10-07 19:32:59 +02:00
public void markNeedsSync ( @NonNull Collection < RecipientId > recipientIds ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
for ( RecipientId recipientId : recipientIds ) {
markDirty ( recipientId , DirtyState . UPDATE ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
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-10-06 06:02:01 +02:00
for ( RecipientId id : storageIds . keySet ( ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-26 16:12:51 +02:00
}
2020-02-28 19:03:06 +01:00
public void applyStorageSyncUpdates ( @NonNull Collection < SignalContactRecord > contactInserts ,
@NonNull Collection < RecordUpdate < SignalContactRecord > > contactUpdates ,
@NonNull Collection < SignalGroupV1Record > groupV1Inserts ,
2020-05-19 23:48:26 +02:00
@NonNull Collection < RecordUpdate < SignalGroupV1Record > > groupV1Updates ,
@NonNull Collection < SignalGroupV2Record > groupV2Inserts ,
@NonNull Collection < RecordUpdate < SignalGroupV2Record > > groupV2Updates )
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 ) ;
2020-07-01 20:53:42 +02:00
Set < RecipientId > needsRefresh = new HashSet < > ( ) ;
2019-09-26 16:12:51 +02:00
db . beginTransaction ( ) ;
try {
2020-02-10 19:42:43 +01:00
for ( SignalContactRecord insert : contactInserts ) {
2020-08-07 23:31:06 +02:00
ContentValues values = getValuesForStorageContact ( insert , true ) ;
long id = db . insertWithOnConflict ( TABLE_NAME , null , values , SQLiteDatabase . CONFLICT_IGNORE ) ;
RecipientId recipientId = null ;
2020-07-16 00:03:18 +02:00
2020-02-28 20:56:33 +01:00
if ( id < 0 ) {
2020-07-21 20:26:40 +02:00
values = getValuesForStorageContact ( insert , false ) ;
2020-02-28 20:56:33 +01:00
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 ( ) ) {
2020-07-16 00:03:18 +02:00
try {
int count = db . update ( TABLE_NAME , values , PHONE + " = ?" , new String [ ] { insert . getAddress ( ) . getNumber ( ) . get ( ) } ) ;
Log . w ( TAG , "Updated " + count + " users by E164." ) ;
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[applyStorageSyncUpdates -- Insert] Failed to update the UUID on an existing E164 user. Possibly merging." ) ;
recipientId = getAndPossiblyMerge ( insert . getAddress ( ) . getUuid ( ) . get ( ) , insert . getAddress ( ) . getNumber ( ) . get ( ) , true ) ;
Log . w ( TAG , "[applyStorageSyncUpdates -- Insert] Resulting id: " + recipientId ) ;
}
2020-08-07 23:31:06 +02:00
}
if ( recipientId = = null & & insert . getAddress ( ) . getUuid ( ) . isPresent ( ) ) {
2020-07-16 00:03:18 +02:00
try {
int count = db . update ( TABLE_NAME , values , UUID + " = ?" , new String [ ] { insert . getAddress ( ) . getUuid ( ) . get ( ) . toString ( ) } ) ;
Log . w ( TAG , "Updated " + count + " users by UUID." ) ;
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[applyStorageSyncUpdates -- Insert] Failed to update the E164 on an existing UUID user. Possibly merging." ) ;
recipientId = getAndPossiblyMerge ( insert . getAddress ( ) . getUuid ( ) . get ( ) , insert . getAddress ( ) . getNumber ( ) . get ( ) , true ) ;
Log . w ( TAG , "[applyStorageSyncUpdates -- Insert] Resulting id: " + recipientId ) ;
}
2020-02-28 20:56:33 +01:00
}
2020-08-07 23:31:06 +02:00
if ( recipientId = = null & & insert . getAddress ( ) . getNumber ( ) . isPresent ( ) ) {
recipientId = getByE164 ( insert . getAddress ( ) . getNumber ( ) . get ( ) ) . orNull ( ) ;
}
if ( recipientId = = null & & insert . getAddress ( ) . getUuid ( ) . isPresent ( ) ) {
recipientId = getByUuid ( insert . getAddress ( ) . getUuid ( ) . get ( ) ) . orNull ( ) ;
}
if ( recipientId = = null ) {
Log . w ( TAG , "Failed to recover from a failed insert!" ) ;
continue ;
}
2020-02-28 20:56:33 +01:00
} else {
2020-07-16 00:03:18 +02:00
recipientId = RecipientId . from ( id ) ;
}
2020-02-28 20:56:33 +01:00
2020-07-16 00:03:18 +02:00
if ( insert . getIdentityKey ( ) . isPresent ( ) ) {
try {
IdentityKey identityKey = new IdentityKey ( insert . getIdentityKey ( ) . get ( ) , 0 ) ;
2020-02-28 20:56:33 +01:00
2020-07-16 00:03:18 +02:00
DatabaseFactory . getIdentityDatabase ( context ) . updateIdentityAfterSync ( recipientId , identityKey , StorageSyncModels . remoteToLocalIdentityStatus ( insert . getIdentityState ( ) ) ) ;
} 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-07-16 00:03:18 +02:00
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipientId , insert ) ;
2020-07-16 00:03:18 +02:00
needsRefresh . add ( recipientId ) ;
2019-09-26 16:12:51 +02:00
}
2020-02-28 19:03:06 +01:00
for ( RecordUpdate < SignalContactRecord > update : contactUpdates ) {
2020-07-16 00:03:18 +02:00
ContentValues values = getValuesForStorageContact ( update . getNew ( ) , false ) ;
2019-09-26 16:12:51 +02:00
2020-07-16 00:03:18 +02:00
try {
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( update . getOld ( ) . getId ( ) . getRaw ( ) ) } ) ;
if ( updateCount < 1 ) {
throw new AssertionError ( "Had an update, but it didn't match any rows!" ) ;
}
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[applyStorageSyncUpdates -- Update] Failed to update a user by storageId." ) ;
RecipientId recipientId = getByColumn ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( update . getOld ( ) . getId ( ) . getRaw ( ) ) ) . get ( ) ;
Log . w ( TAG , "[applyStorageSyncUpdates -- Update] Found user " + recipientId + ". Possibly merging." ) ;
recipientId = getAndPossiblyMerge ( update . getNew ( ) . getAddress ( ) . getUuid ( ) . orNull ( ) , update . getNew ( ) . getAddress ( ) . getNumber ( ) . orNull ( ) , true ) ;
Log . w ( TAG , "[applyStorageSyncUpdates -- Update] Merged into " + recipientId ) ;
db . update ( TABLE_NAME , values , ID_WHERE , SqlUtil . buildArgs ( recipientId ) ) ;
2019-09-26 16:12:51 +02:00
}
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 ) ;
2020-10-06 16:24:14 +02:00
if ( ( newIdentityRecord . isPresent ( ) & & newIdentityRecord . get ( ) . getVerifiedStatus ( ) = = VerifiedStatus . VERIFIED ) & &
( ! oldIdentityRecord . isPresent ( ) | | oldIdentityRecord . get ( ) . getVerifiedStatus ( ) ! = VerifiedStatus . VERIFIED ) )
2019-09-26 16:12:51 +02:00
{
IdentityUtil . markIdentityVerified ( context , Recipient . resolved ( recipientId ) , true , true ) ;
2020-10-06 16:24:14 +02:00
} else if ( ( newIdentityRecord . isPresent ( ) & & newIdentityRecord . get ( ) . getVerifiedStatus ( ) ! = VerifiedStatus . VERIFIED ) & &
( oldIdentityRecord . isPresent ( ) & & oldIdentityRecord . get ( ) . getVerifiedStatus ( ) = = VerifiedStatus . VERIFIED ) )
2019-09-26 16:12:51 +02:00
{
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
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipientId , update . getNew ( ) ) ;
2020-07-01 20:53:42 +02:00
needsRefresh . add ( recipientId ) ;
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-04-20 17:01:31 +02:00
Recipient recipient = Recipient . externalGroup ( context , GroupId . v1orThrow ( insert . getGroupId ( ) ) ) ;
2020-03-18 21:31:45 +01:00
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipient . getId ( ) , insert ) ;
2020-07-01 20:53:42 +02:00
needsRefresh . add ( recipient . getId ( ) ) ;
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-04-20 17:01:31 +02:00
Recipient recipient = Recipient . externalGroup ( context , GroupId . v1orThrow ( update . getOld ( ) . getGroupId ( ) ) ) ;
2020-03-18 21:31:45 +01:00
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipient . getId ( ) , update . getNew ( ) ) ;
2020-07-01 20:53:42 +02:00
needsRefresh . add ( recipient . getId ( ) ) ;
2020-02-10 19:42:43 +01:00
}
2020-05-19 23:48:26 +02:00
for ( SignalGroupV2Record insert : groupV2Inserts ) {
2020-09-10 19:39:29 +02:00
GroupMasterKey masterKey = insert . getMasterKeyOrThrow ( ) ;
GroupId . V2 groupId = GroupId . v2 ( masterKey ) ;
2020-09-30 15:11:51 +02:00
ContentValues values = getValuesForStorageGroupV2 ( insert ) ;
long id = db . insertWithOnConflict ( TABLE_NAME , null , values , SQLiteDatabase . CONFLICT_IGNORE ) ;
2020-09-10 19:39:29 +02:00
Recipient recipient = Recipient . externalGroup ( context , groupId ) ;
2020-05-19 23:48:26 +02:00
2020-09-30 15:11:51 +02:00
if ( id < 0 ) {
Log . w ( TAG , String . format ( "Recipient %s is already linked to group %s" , recipient . getId ( ) , groupId ) ) ;
} else {
Log . i ( TAG , String . format ( "Inserted recipient %s for group %s" , recipient . getId ( ) , groupId ) ) ;
}
2020-09-09 16:59:09 +02:00
2020-09-30 15:11:51 +02:00
Log . i ( TAG , "Creating restore placeholder for " + groupId ) ;
2020-09-09 16:59:09 +02:00
DatabaseFactory . getGroupDatabase ( context )
2020-09-10 19:39:29 +02:00
. create ( masterKey ,
2020-09-09 16:59:09 +02:00
DecryptedGroup . newBuilder ( )
. setRevision ( GroupsV2StateProcessor . RESTORE_PLACEHOLDER_REVISION )
. build ( ) ) ;
Log . i ( TAG , "Scheduling request for latest group info for " + groupId ) ;
ApplicationDependencies . getJobManager ( ) . add ( new RequestGroupV2InfoJob ( groupId ) ) ;
2020-05-19 23:48:26 +02:00
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipient . getId ( ) , insert ) ;
2020-07-01 20:53:42 +02:00
needsRefresh . add ( recipient . getId ( ) ) ;
2020-05-19 23:48:26 +02:00
}
for ( RecordUpdate < SignalGroupV2Record > update : groupV2Updates ) {
ContentValues values = getValuesForStorageGroupV2 ( update . getNew ( ) ) ;
int updateCount = db . update ( TABLE_NAME , values , STORAGE_SERVICE_ID + " = ?" , new String [ ] { Base64 . encodeBytes ( update . getOld ( ) . getId ( ) . getRaw ( ) ) } ) ;
if ( updateCount < 1 ) {
throw new AssertionError ( "Had an update, but it didn't match any rows!" ) ;
}
2020-09-10 19:39:29 +02:00
GroupMasterKey masterKey = update . getOld ( ) . getMasterKeyOrThrow ( ) ;
Recipient recipient = Recipient . externalGroup ( context , GroupId . v2 ( masterKey ) ) ;
2020-05-19 23:48:26 +02:00
2020-10-07 19:32:59 +02:00
threadDatabase . applyStorageSyncUpdate ( recipient . getId ( ) , update . getNew ( ) ) ;
2020-07-01 20:53:42 +02:00
needsRefresh . add ( recipient . getId ( ) ) ;
2020-05-19 23:48:26 +02:00
}
2020-02-10 19:42:43 +01:00
2019-09-26 16:12:51 +02:00
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
2020-07-01 20:53:42 +02:00
for ( RecipientId id : needsRefresh ) {
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-26 16:12:51 +02:00
}
2020-03-18 21:31:45 +01:00
public void applyStorageSyncUpdates ( @NonNull StorageId storageId , SignalAccountRecord update ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
2020-07-16 15:33:47 +02:00
ContentValues values = new ContentValues ( ) ;
ProfileName profileName = ProfileName . fromParts ( update . getGivenName ( ) . orNull ( ) , update . getFamilyName ( ) . orNull ( ) ) ;
Optional < ProfileKey > localKey = ProfileKeyUtil . profileKeyOptional ( Recipient . self ( ) . getProfileKey ( ) ) ;
Optional < ProfileKey > remoteKey = ProfileKeyUtil . profileKeyOptional ( update . getProfileKey ( ) . orNull ( ) ) ;
String profileKey = remoteKey . or ( localKey ) . transform ( ProfileKey : : serialize ) . transform ( Base64 : : encodeBytes ) . orNull ( ) ;
2020-06-01 18:55:30 +02:00
2020-07-16 15:33:47 +02:00
if ( ! remoteKey . isPresent ( ) ) {
2020-06-01 18:55:30 +02:00
Log . w ( TAG , "Got an empty profile key while applying an account record update!" ) ;
}
2020-03-18 21:31:45 +01:00
values . put ( PROFILE_GIVEN_NAME , profileName . getGivenName ( ) ) ;
values . put ( PROFILE_FAMILY_NAME , profileName . getFamilyName ( ) ) ;
values . put ( PROFILE_JOINED_NAME , profileName . toString ( ) ) ;
2020-06-01 18:55:30 +02:00
values . put ( PROFILE_KEY , profileKey ) ;
2020-03-18 21:31:45 +01:00
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( update . getId ( ) . getRaw ( ) ) ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
2020-08-20 22:05:50 +02:00
if ( update . hasUnknownFields ( ) ) {
values . put ( STORAGE_PROTO , Base64 . encodeBytes ( update . serializeUnknownFields ( ) ) ) ;
} else {
values . putNull ( STORAGE_PROTO ) ;
}
2020-03-18 21:31:45 +01:00
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!" ) ;
}
2020-07-16 15:33:47 +02:00
if ( ! remoteKey . equals ( localKey ) ) {
ApplicationDependencies . getJobManager ( ) . add ( new RefreshAttributesJob ( ) ) ;
}
2020-10-07 19:32:59 +02:00
DatabaseFactory . getThreadDatabase ( context ) . applyStorageSyncUpdate ( Recipient . self ( ) . getId ( ) , update ) ;
2020-03-18 21:31:45 +01:00
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!" ) ;
}
}
}
2020-06-10 16:49:22 +02:00
private static @NonNull ContentValues getValuesForStorageContact ( @NonNull SignalContactRecord contact , boolean isInsert ) {
2019-09-26 16:12:51 +02:00
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 ( ) ) ;
2020-06-10 16:49:22 +02:00
2020-09-22 19:09:48 +02:00
if ( contact . isProfileSharingEnabled ( ) & & isInsert & & ! profileName . isEmpty ( ) ) {
2020-06-10 16:49:22 +02:00
values . put ( COLOR , ContactColors . generateFor ( profileName . toString ( ) ) . serialize ( ) ) ;
}
2020-08-20 22:05:50 +02:00
if ( contact . hasUnknownFields ( ) ) {
values . put ( STORAGE_PROTO , Base64 . encodeBytes ( contact . serializeUnknownFields ( ) ) ) ;
} else {
values . putNull ( STORAGE_PROTO ) ;
}
2019-09-26 16:12:51 +02:00
return values ;
}
2020-02-10 19:42:43 +01:00
private static @NonNull ContentValues getValuesForStorageGroupV1 ( @NonNull SignalGroupV1Record groupV1 ) {
ContentValues values = new ContentValues ( ) ;
2020-04-20 17:01:31 +02:00
values . put ( GROUP_ID , GroupId . v1orThrow ( 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 ( ) ) ;
2020-08-20 22:05:50 +02:00
if ( groupV1 . hasUnknownFields ( ) ) {
values . put ( STORAGE_PROTO , Base64 . encodeBytes ( groupV1 . serializeUnknownFields ( ) ) ) ;
} else {
values . putNull ( STORAGE_PROTO ) ;
}
2020-02-10 19:42:43 +01:00
return values ;
}
2020-05-19 23:48:26 +02:00
private static @NonNull ContentValues getValuesForStorageGroupV2 ( @NonNull SignalGroupV2Record groupV2 ) {
ContentValues values = new ContentValues ( ) ;
2020-09-10 19:39:29 +02:00
values . put ( GROUP_ID , GroupId . v2 ( groupV2 . getMasterKeyOrThrow ( ) ) . toString ( ) ) ;
2020-05-19 23:48:26 +02:00
values . put ( GROUP_TYPE , GroupType . SIGNAL_V2 . getId ( ) ) ;
values . put ( PROFILE_SHARING , groupV2 . isProfileSharingEnabled ( ) ? "1" : "0" ) ;
values . put ( BLOCKED , groupV2 . isBlocked ( ) ? "1" : "0" ) ;
values . put ( STORAGE_SERVICE_ID , Base64 . encodeBytes ( groupV2 . getId ( ) . getRaw ( ) ) ) ;
values . put ( DIRTY , DirtyState . CLEAN . getId ( ) ) ;
2020-08-20 22:05:50 +02:00
if ( groupV2 . hasUnknownFields ( ) ) {
values . put ( STORAGE_PROTO , Base64 . encodeBytes ( groupV2 . serializeUnknownFields ( ) ) ) ;
} else {
values . putNull ( STORAGE_PROTO ) ;
}
2020-05-19 23:48:26 +02:00
return values ;
}
2020-02-10 19:42:43 +01:00
2020-05-19 23:48:26 +02:00
private List < RecipientSettings > getRecipientSettingsForSync ( @Nullable String query , @Nullable String [ ] args ) {
2019-09-26 16:12:51 +02:00
SQLiteDatabase db = databaseHelper . getReadableDatabase ( ) ;
2020-10-06 16:24:14 +02:00
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase . TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . RECIPIENT_ID
+ " LEFT OUTER JOIN " + GroupDatabase . TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase . TABLE_NAME + "." + GroupDatabase . GROUP_ID
+ " LEFT OUTER JOIN " + ThreadDatabase . TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . RECIPIENT_ID ;
2019-09-26 16:12:51 +02:00
List < RecipientSettings > out = new ArrayList < > ( ) ;
2020-10-06 16:24:14 +02:00
String [ ] columns = Stream . of ( TYPED_RECIPIENT_PROJECTION ,
new String [ ] { RecipientDatabase . TABLE_NAME + "." + STORAGE_PROTO ,
GroupDatabase . TABLE_NAME + "." + GroupDatabase . V2_MASTER_KEY ,
ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . ARCHIVED ,
ThreadDatabase . TABLE_NAME + "." + ThreadDatabase . READ ,
IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . VERIFIED + " AS " + IDENTITY_STATUS ,
IdentityDatabase . TABLE_NAME + "." + IdentityDatabase . IDENTITY_KEY + " AS " + IDENTITY_KEY } )
. flatMap ( Stream : : of )
. toArray ( String [ ] : : new ) ;
2020-05-19 23:48:26 +02:00
2020-10-09 18:16:38 +02:00
try ( Cursor cursor = db . query ( table , columns , query , args , TABLE_NAME + "." + ID , null , null ) ) {
2019-09-26 16:12:51 +02:00
while ( cursor ! = null & & cursor . moveToNext ( ) ) {
2020-04-22 16:13:56 +02:00
out . add ( getRecipientSettings ( context , cursor ) ) ;
2019-09-26 16:12:51 +02:00
}
}
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 ( ) ;
2020-07-13 16:52:06 +02:00
String query = STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " != ? AND " + ID + " != ? AND " + GROUP_TYPE + " != ?" ;
2020-09-18 19:35:20 +02:00
String [ ] args = { String . valueOf ( DirtyState . DELETE . getId ( ) ) , Recipient . self ( ) . getId ( ) . serialize ( ) , String . valueOf ( GroupType . SIGNAL_V2 . getId ( ) ) } ;
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-05-19 23:48:26 +02:00
switch ( groupType ) {
case NONE : out . put ( id , StorageId . forContact ( key ) ) ; break ;
case SIGNAL_V1 : out . put ( id , StorageId . forGroupV1 ( key ) ) ; break ;
default : throw new AssertionError ( ) ;
2020-02-28 19:03:06 +01:00
}
2019-09-26 16:12:51 +02:00
}
}
2020-07-13 16:52:06 +02:00
for ( GroupId . V2 id : DatabaseFactory . getGroupDatabase ( context ) . getAllGroupV2Ids ( ) ) {
Recipient recipient = Recipient . externalGroup ( context , id ) ;
RecipientId recipientId = recipient . getId ( ) ;
RecipientSettings recipientSettingsForSync = getRecipientSettingsForSync ( recipientId ) ;
if ( recipientSettingsForSync = = null ) {
throw new AssertionError ( ) ;
}
byte [ ] key = recipientSettingsForSync . storageId ;
if ( key = = null ) {
throw new AssertionError ( ) ;
}
out . put ( recipientId , StorageId . forGroupV2 ( key ) ) ;
}
2019-09-26 16:12:51 +02:00
return out ;
}
2020-07-02 17:19:52 +02:00
static @NonNull RecipientSettings getRecipientSettings ( @NonNull Context context , @NonNull Cursor cursor ) {
long id = CursorUtil . requireLong ( cursor , ID ) ;
UUID uuid = UuidUtil . parseOrNull ( CursorUtil . requireString ( cursor , UUID ) ) ;
String username = CursorUtil . requireString ( cursor , USERNAME ) ;
String e164 = CursorUtil . requireString ( cursor , PHONE ) ;
String email = CursorUtil . requireString ( cursor , EMAIL ) ;
GroupId groupId = GroupId . parseNullableOrThrow ( CursorUtil . requireString ( cursor , GROUP_ID ) ) ;
int groupType = CursorUtil . requireInt ( cursor , GROUP_TYPE ) ;
boolean blocked = CursorUtil . requireBoolean ( cursor , BLOCKED ) ;
String messageRingtone = CursorUtil . requireString ( cursor , MESSAGE_RINGTONE ) ;
String callRingtone = CursorUtil . requireString ( cursor , CALL_RINGTONE ) ;
int messageVibrateState = CursorUtil . requireInt ( cursor , MESSAGE_VIBRATE ) ;
int callVibrateState = CursorUtil . requireInt ( cursor , CALL_VIBRATE ) ;
2020-02-11 00:40:22 +01:00
long muteUntil = cursor . getLong ( cursor . getColumnIndexOrThrow ( MUTE_UNTIL ) ) ;
2020-07-02 17:19:52 +02:00
String serializedColor = CursorUtil . requireString ( cursor , COLOR ) ;
int insightsBannerTier = CursorUtil . requireInt ( cursor , SEEN_INVITE_REMINDER ) ;
int defaultSubscriptionId = CursorUtil . requireInt ( cursor , DEFAULT_SUBSCRIPTION_ID ) ;
int expireMessages = CursorUtil . requireInt ( cursor , MESSAGE_EXPIRATION_TIME ) ;
int registeredState = CursorUtil . requireInt ( cursor , REGISTERED ) ;
String profileKeyString = CursorUtil . requireString ( cursor , PROFILE_KEY ) ;
String profileKeyCredentialString = CursorUtil . requireString ( cursor , PROFILE_KEY_CREDENTIAL ) ;
String systemDisplayName = CursorUtil . requireString ( cursor , SYSTEM_DISPLAY_NAME ) ;
String systemContactPhoto = CursorUtil . requireString ( cursor , SYSTEM_PHOTO_URI ) ;
String systemPhoneLabel = CursorUtil . requireString ( cursor , SYSTEM_PHONE_LABEL ) ;
String systemContactUri = CursorUtil . requireString ( cursor , SYSTEM_CONTACT_URI ) ;
String profileGivenName = CursorUtil . requireString ( cursor , PROFILE_GIVEN_NAME ) ;
String profileFamilyName = CursorUtil . requireString ( cursor , PROFILE_FAMILY_NAME ) ;
String signalProfileAvatar = CursorUtil . requireString ( cursor , SIGNAL_PROFILE_AVATAR ) ;
boolean profileSharing = CursorUtil . requireBoolean ( cursor , PROFILE_SHARING ) ;
2020-06-12 19:22:46 +02:00
long lastProfileFetch = cursor . getLong ( cursor . getColumnIndexOrThrow ( LAST_PROFILE_FETCH ) ) ;
2020-07-02 17:19:52 +02:00
String notificationChannel = CursorUtil . requireString ( cursor , NOTIFICATION_CHANNEL ) ;
int unidentifiedAccessMode = CursorUtil . requireInt ( cursor , UNIDENTIFIED_ACCESS_MODE ) ;
boolean forceSmsSelection = CursorUtil . requireBoolean ( cursor , FORCE_SMS_SELECTION ) ;
int uuidCapabilityValue = CursorUtil . requireInt ( cursor , UUID_CAPABILITY ) ;
int groupsV2CapabilityValue = CursorUtil . requireInt ( cursor , GROUPS_V2_CAPABILITY ) ;
String storageKeyRaw = CursorUtil . requireString ( cursor , STORAGE_SERVICE_ID ) ;
2020-08-05 22:45:52 +02:00
int mentionSettingId = CursorUtil . requireInt ( cursor , MENTION_SETTING ) ;
2020-05-19 23:48:26 +02:00
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-10-06 16:24:14 +02:00
byte [ ] storageKey = storageKeyRaw ! = null ? Base64 . decodeOrThrow ( storageKeyRaw ) : null ;
2019-09-26 16:12:51 +02:00
2020-10-06 16:24:14 +02: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 ) ,
2020-10-06 16:24:14 +02:00
Util . uri ( messageRingtone ) ,
Util . uri ( callRingtone ) ,
color ,
defaultSubscriptionId ,
expireMessages ,
2019-08-07 20:22:51 +02:00
RegisteredState . fromId ( registeredState ) ,
2020-10-06 16:24:14 +02:00
profileKey ,
profileKeyCredential ,
systemDisplayName ,
systemContactPhoto ,
systemPhoneLabel ,
systemContactUri ,
ProfileName . fromParts ( profileGivenName , profileFamilyName ) ,
signalProfileAvatar ,
AvatarHelper . hasAvatar ( context , RecipientId . from ( id ) ) ,
profileSharing ,
lastProfileFetch ,
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 ) ,
2020-10-06 16:24:14 +02:00
storageKey ,
MentionSetting . fromId ( mentionSettingId ) ,
getSyncExtras ( cursor ) ) ;
}
private static @NonNull RecipientSettings . SyncExtras getSyncExtras ( @NonNull Cursor cursor ) {
String storageProtoRaw = CursorUtil . getString ( cursor , STORAGE_PROTO ) . orNull ( ) ;
byte [ ] storageProto = storageProtoRaw ! = null ? Base64 . decodeOrThrow ( storageProtoRaw ) : null ;
boolean archived = CursorUtil . getBoolean ( cursor , ThreadDatabase . ARCHIVED ) . or ( false ) ;
2020-10-07 19:32:59 +02:00
boolean forcedUnread = CursorUtil . getInt ( cursor , ThreadDatabase . READ ) . transform ( status - > status = = ThreadDatabase . ReadStatus . FORCED_UNREAD . serialize ( ) ) . or ( false ) ;
2020-10-06 16:24:14 +02:00
GroupMasterKey groupMasterKey = CursorUtil . getBlob ( cursor , GroupDatabase . V2_MASTER_KEY ) . transform ( GroupUtil : : requireMasterKey ) . orNull ( ) ;
byte [ ] identityKey = CursorUtil . getString ( cursor , IDENTITY_KEY ) . transform ( Base64 : : decodeOrThrow ) . orNull ( ) ;
VerifiedStatus identityStatus = CursorUtil . getInt ( cursor , IDENTITY_STATUS ) . transform ( VerifiedStatus : : forState ) . or ( VerifiedStatus . DEFAULT ) ;
2020-10-07 19:32:59 +02:00
return new RecipientSettings . SyncExtras ( storageProto , groupMasterKey , identityKey , identityStatus , archived , forcedUnread ) ;
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
}
2020-06-10 16:49:22 +02:00
public void setColorIfNotSet ( @NonNull RecipientId id , @NonNull MaterialColor color ) {
if ( setColorIfNotSetInternal ( id , color ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
}
private boolean setColorIfNotSetInternal ( @NonNull RecipientId id , @NonNull MaterialColor color ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
String query = ID + " = ? AND " + COLOR + " IS NULL" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
ContentValues values = new ContentValues ( ) ;
values . put ( COLOR , color . serialize ( ) ) ;
return db . update ( TABLE_NAME , values , query , args ) > 0 ;
}
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 ) ;
2020-06-07 18:22:29 +02:00
if ( 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 ) ;
2020-06-07 18:22:29 +02:00
Recipient . live ( id ) . refresh ( ) ;
2019-09-26 16:12:51 +02:00
}
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 ( ) ) ;
2020-06-07 18:22:29 +02:00
if ( update ( id , values ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-07 05:40:06 +02:00
}
2020-08-05 22:45:52 +02:00
public void setMentionSetting ( @NonNull RecipientId id , @NonNull MentionSetting mentionSetting ) {
ContentValues values = new ContentValues ( ) ;
values . put ( MENTION_SETTING , mentionSetting . getId ( ) ) ;
if ( 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
2020-10-07 19:32:59 +02:00
SqlUtil . Query updateQuery = SqlUtil . buildTrueUpdateQuery ( selection , args , valuesToCompare ) ;
2020-02-11 00:40:22 +01:00
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 ;
2020-05-02 00:13:23 +02:00
}
return false ;
}
/ * *
* Sets the profile key iff currently null .
* < p >
* If it sets it , it also clears out the profile key credential and resets the unidentified access mode .
* @return true iff changed .
* /
public boolean setProfileKeyIfAbsent ( @NonNull RecipientId id , @NonNull ProfileKey profileKey ) {
SQLiteDatabase database = databaseHelper . getWritableDatabase ( ) ;
String selection = ID + " = ? AND " + PROFILE_KEY + " is NULL" ;
String [ ] args = new String [ ] { id . serialize ( ) } ;
ContentValues valuesToSet = new ContentValues ( 3 ) ;
valuesToSet . put ( PROFILE_KEY , Base64 . encodeBytes ( profileKey . serialize ( ) ) ) ;
valuesToSet . putNull ( PROFILE_KEY_CREDENTIAL ) ;
valuesToSet . put ( UNIDENTIFIED_ACCESS_MODE , UnidentifiedAccessMode . UNKNOWN . getMode ( ) ) ;
if ( database . update ( TABLE_NAME , valuesToSet , selection , args ) > 0 ) {
markDirty ( id , DirtyState . UPDATE ) ;
Recipient . live ( id ) . refresh ( ) ;
return true ;
2020-02-12 22:30:20 +01:00
} 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 ( ) ) ) ;
2020-10-07 19:32:59 +02:00
SqlUtil . Query updateQuery = SqlUtil . buildTrueUpdateQuery ( selection , args , values ) ;
2020-02-11 00:40:22 +01:00
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
}
2020-05-02 00:13:23 +02:00
/ * *
* Fills in gaps ( nulls ) in profile key knowledge from new profile keys .
* < p >
* If from authoritative source , this will overwrite local , otherwise it will only write to the
* database if missing .
* /
2020-07-24 21:53:49 +02:00
public Set < RecipientId > persistProfileKeySet ( @NonNull ProfileKeySet profileKeySet ) {
2020-05-02 00:13:23 +02:00
Map < UUID , ProfileKey > profileKeys = profileKeySet . getProfileKeys ( ) ;
Map < UUID , ProfileKey > authoritativeProfileKeys = profileKeySet . getAuthoritativeProfileKeys ( ) ;
int totalKeys = profileKeys . size ( ) + authoritativeProfileKeys . size ( ) ;
if ( totalKeys = = 0 ) {
2020-07-24 21:53:49 +02:00
return Collections . emptySet ( ) ;
2020-05-02 00:13:23 +02:00
}
Log . i ( TAG , String . format ( Locale . US , "Persisting %d Profile keys, %d of which are authoritative" , totalKeys , authoritativeProfileKeys . size ( ) ) ) ;
HashSet < RecipientId > updated = new HashSet < > ( totalKeys ) ;
RecipientId selfId = Recipient . self ( ) . getId ( ) ;
for ( Map . Entry < UUID , ProfileKey > entry : profileKeys . entrySet ( ) ) {
RecipientId recipientId = getOrInsertFromUuid ( entry . getKey ( ) ) ;
if ( setProfileKeyIfAbsent ( recipientId , entry . getValue ( ) ) ) {
Log . i ( TAG , "Learned new profile key" ) ;
updated . add ( recipientId ) ;
}
}
for ( Map . Entry < UUID , ProfileKey > entry : authoritativeProfileKeys . entrySet ( ) ) {
RecipientId recipientId = getOrInsertFromUuid ( entry . getKey ( ) ) ;
if ( selfId . equals ( recipientId ) ) {
Log . i ( TAG , "Seen authoritative update for self" ) ;
if ( ! entry . getValue ( ) . equals ( ProfileKeyUtil . getSelfProfileKey ( ) ) ) {
Log . w ( TAG , "Seen authoritative update for self that didn't match local, scheduling storage sync" ) ;
StorageSyncHelper . scheduleSyncForDataChange ( ) ;
}
} else {
Log . i ( TAG , String . format ( "Profile key from owner %s" , recipientId ) ) ;
if ( setProfileKey ( recipientId , entry . getValue ( ) ) ) {
Log . i ( TAG , "Learned new profile key from owner" ) ;
updated . add ( recipientId ) ;
}
}
}
return updated ;
}
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 ) ;
2020-06-10 16:49:22 +02:00
boolean profiledUpdated = update ( id , contentValues ) ;
boolean colorUpdated = enabled & & setColorIfNotSetInternal ( id , ContactColors . generateFor ( Recipient . resolved ( id ) . getDisplayName ( context ) ) ) ;
if ( profiledUpdated | | colorUpdated ) {
2019-09-26 16:12:51 +02:00
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
}
2020-07-16 00:03:18 +02:00
/ * *
* @return True if setting the phone number resulted in changed recipientId , otherwise false .
* /
public boolean setPhoneNumber ( @NonNull RecipientId id , @NonNull String e164 ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
setPhoneNumberOrThrow ( id , e164 ) ;
db . setTransactionSuccessful ( ) ;
return false ;
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[setPhoneNumber] Hit a conflict when trying to update " + id + ". Possibly merging." ) ;
RecipientSettings existing = getRecipientSettings ( id ) ;
RecipientId newId = getAndPossiblyMerge ( existing . getUuid ( ) , e164 , true ) ;
Log . w ( TAG , "[setPhoneNumber] Resulting id: " + newId ) ;
db . setTransactionSuccessful ( ) ;
return ! newId . equals ( existing . getId ( ) ) ;
} finally {
db . endTransaction ( ) ;
}
}
private void removePhoneNumber ( @NonNull RecipientId recipientId , @NonNull SQLiteDatabase db ) {
ContentValues values = new ContentValues ( ) ;
values . putNull ( PHONE ) ;
db . update ( TABLE_NAME , values , ID_WHERE , SqlUtil . buildArgs ( recipientId ) ) ;
}
/ * *
* Should only use if you are confident that this will not result in any contact merging .
* /
public void setPhoneNumberOrThrow ( @NonNull RecipientId id , @NonNull String e164 ) {
2019-09-07 05:40:06 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( PHONE , e164 ) ;
2020-07-16 00:03:18 +02:00
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 ;
}
2020-07-16 00:03:18 +02:00
/ * *
* @return True if setting the UUID resulted in changed recipientId , otherwise false .
* /
public boolean markRegistered ( @NonNull RecipientId id , @NonNull UUID uuid ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
2020-07-02 19:38:52 +02:00
2020-07-16 00:03:18 +02:00
try {
markRegisteredOrThrow ( id , uuid ) ;
db . setTransactionSuccessful ( ) ;
return false ;
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[markRegistered] Hit a conflict when trying to update " + id + ". Possibly merging." ) ;
RecipientSettings existing = getRecipientSettings ( id ) ;
RecipientId newId = getAndPossiblyMerge ( uuid , existing . getE164 ( ) , true ) ;
Log . w ( TAG , "[markRegistered] Merged into " + newId ) ;
db . setTransactionSuccessful ( ) ;
return ! newId . equals ( existing . getId ( ) ) ;
} finally {
db . endTransaction ( ) ;
2020-07-02 19:38:52 +02:00
}
2020-07-16 00:03:18 +02:00
}
/ * *
* Should only use if you are confident that this shouldn ' t result in any contact merging .
* /
public void markRegisteredOrThrow ( @NonNull RecipientId id , @NonNull UUID uuid ) {
ContentValues contentValues = new ContentValues ( 2 ) ;
contentValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
contentValues . put ( UUID , uuid . toString ( ) . toLowerCase ( ) ) ;
2020-07-02 19:38:52 +02:00
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 ) {
2020-07-02 19:38:52 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
2019-09-07 05:40:06 +02:00
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 ( ) ) ;
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 ( ) ) ;
2020-07-02 19:38:52 +02:00
if ( entry . getValue ( ) ! = null ) {
values . put ( UUID , entry . getValue ( ) . toLowerCase ( ) ) ;
}
2020-07-16 00:03:18 +02:00
try {
if ( update ( entry . getKey ( ) , values ) ) {
markDirty ( entry . getKey ( ) , DirtyState . INSERT ) ;
}
} catch ( SQLiteConstraintException e ) {
Log . w ( TAG , "[bulkUpdateRegisteredStatus] Hit a conflict when trying to update " + entry . getKey ( ) + ". Possibly merging." ) ;
RecipientSettings existing = getRecipientSettings ( entry . getKey ( ) ) ;
RecipientId newId = getAndPossiblyMerge ( UuidUtil . parseOrThrow ( entry . getValue ( ) ) , existing . getE164 ( ) , true ) ;
Log . w ( TAG , "[bulkUpdateRegisteredStatus] Merged into " + newId ) ;
2019-09-26 16:12:51 +02:00
}
2019-09-07 05:40:06 +02:00
}
for ( RecipientId id : unregistered ) {
2020-07-02 19:38:52 +02:00
ContentValues values = new ContentValues ( 2 ) ;
2019-09-07 05:40:06 +02:00
values . put ( REGISTERED , RegisteredState . NOT_REGISTERED . getId ( ) ) ;
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-04-25 00:37:12 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
2017-08-22 19:44:04 +02:00
contentValues . put ( REGISTERED , registeredState . getId ( ) ) ;
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
}
}
2020-09-22 22:24:13 +02:00
/ * *
* Handles inserts the ( e164 , UUID ) pairs , which could result in merges . Does not mark users as
* registered .
*
* @return A mapping of ( RecipientId , UUID )
* /
2020-07-16 00:03:18 +02:00
public @NonNull Map < RecipientId , String > bulkProcessCdsResult ( @NonNull Map < String , UUID > mapping ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
HashMap < RecipientId , String > uuidMap = new HashMap < > ( ) ;
db . beginTransaction ( ) ;
try {
for ( Map . Entry < String , UUID > entry : mapping . entrySet ( ) ) {
String e164 = entry . getKey ( ) ;
UUID uuid = entry . getValue ( ) ;
Optional < RecipientId > uuidEntry = uuid ! = null ? getByUuid ( uuid ) : Optional . absent ( ) ;
if ( uuidEntry . isPresent ( ) ) {
boolean idChanged = setPhoneNumber ( uuidEntry . get ( ) , e164 ) ;
if ( idChanged ) {
uuidEntry = getByUuid ( Objects . requireNonNull ( uuid ) ) ;
}
}
RecipientId id = uuidEntry . isPresent ( ) ? uuidEntry . get ( ) : getOrInsertFromE164 ( e164 ) ;
uuidMap . put ( id , uuid ! = null ? uuid . toString ( ) : null ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
return uuidMap ;
}
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
}
}
2020-05-13 18:41:36 +02:00
public @Nullable Cursor getSignalContacts ( boolean includeSelf ) {
2020-05-22 19:42:19 +02:00
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
2020-04-27 20:09:16 +02:00
"(" + SORT_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)" ;
2020-05-13 18:41:36 +02:00
String [ ] args ;
if ( includeSelf ) {
2020-05-22 19:42:19 +02:00
args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" } ;
2020-05-13 18:41:36 +02:00
} else {
selection + = " AND " + ID + " != ?" ;
2020-05-26 21:25:03 +02:00
args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" , Recipient . self ( ) . getId ( ) . serialize ( ) } ;
2020-05-13 18:41:36 +02:00
}
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 ) ;
}
2020-05-13 18:41:36 +02:00
public @Nullable Cursor querySignalContacts ( @NonNull String query , boolean includeSelf ) {
2020-07-23 21:57:10 +02:00
query = buildCaseInsensitiveGlobPattern ( query ) ;
2019-08-27 00:09:01 +02:00
2020-04-27 20:09:16 +02:00
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
2020-05-22 19:42:19 +02:00
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
2019-08-27 00:09:01 +02:00
"(" +
2020-07-23 04:36:10 +02:00
PHONE + " GLOB ? OR " +
SORT_NAME + " GLOB ? OR " +
USERNAME + " GLOB ?" +
2019-08-27 00:09:01 +02:00
")" ;
2020-05-13 18:41:36 +02:00
String [ ] args ;
if ( includeSelf ) {
2020-05-22 19:42:19 +02:00
args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" , query , query , query } ;
2020-05-13 18:41:36 +02:00
} else {
selection + = " AND " + ID + " != ?" ;
2020-05-22 19:42:19 +02:00
args = new String [ ] { "0" , String . valueOf ( RegisteredState . REGISTERED . getId ( ) ) , "1" , query , query , query , String . valueOf ( Recipient . self ( ) . getId ( ) . toLong ( ) ) } ;
2020-05-13 18:41:36 +02:00
}
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 ) {
2020-07-23 21:57:10 +02:00
query = buildCaseInsensitiveGlobPattern ( query ) ;
2019-08-27 00:09:01 +02:00
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 " +
"(" +
2020-07-23 04:36:10 +02:00
PHONE + " GLOB ? OR " +
EMAIL + " GLOB ? OR " +
SYSTEM_DISPLAY_NAME + " GLOB ?" +
2019-08-27 00:09:01 +02:00
")" ;
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 ) {
2020-07-23 21:57:10 +02:00
query = buildCaseInsensitiveGlobPattern ( query ) ;
2019-11-14 20:35:08 +01:00
String selection = BLOCKED + " = ? AND " +
"(" +
2020-07-23 04:36:10 +02:00
SORT_NAME + " GLOB ? OR " +
USERNAME + " GLOB ? OR " +
PHONE + " GLOB ? OR " +
EMAIL + " GLOB ?" +
2019-11-14 20:35:08 +01:00
")" ;
String [ ] args = new String [ ] { "0" , query , query , query , query } ;
return databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , SEARCH_PROJECTION , selection , args , null , null , null ) ;
}
2020-08-07 21:18:40 +02:00
public @NonNull List < Recipient > queryRecipientsForMentions ( @NonNull String query ) {
return queryRecipientsForMentions ( query , null ) ;
}
public @NonNull List < Recipient > queryRecipientsForMentions ( @NonNull String query , @Nullable List < RecipientId > recipientIds ) {
2020-08-05 22:45:52 +02:00
query = buildCaseInsensitiveGlobPattern ( query ) ;
2020-08-07 21:18:40 +02:00
String ids = null ;
if ( Util . hasItems ( recipientIds ) ) {
ids = TextUtils . join ( "," , Stream . of ( recipientIds ) . map ( RecipientId : : serialize ) . toList ( ) ) ;
}
2020-08-05 22:45:52 +02:00
String selection = BLOCKED + " = 0 AND " +
2020-08-07 21:18:40 +02:00
( ids ! = null ? ID + " IN (" + ids + ") AND " : "" ) +
2020-08-05 22:45:52 +02:00
SORT_NAME + " GLOB ?" ;
List < Recipient > recipients = new ArrayList < > ( ) ;
try ( RecipientDatabase . RecipientReader reader = new RecipientReader ( databaseHelper . getReadableDatabase ( ) . query ( TABLE_NAME , MENTION_SEARCH_PROJECTION , selection , SqlUtil . buildArgs ( query ) , null , null , SORT_NAME ) ) ) {
Recipient recipient ;
while ( ( recipient = reader . getNext ( ) ) ! = null ) {
recipients . add ( recipient ) ;
}
}
return recipients ;
}
2020-07-23 21:57:10 +02:00
/ * *
* Builds a case - insensitive GLOB pattern for fuzzy text queries . Works with all unicode
* characters .
*
* Ex :
* cat - > [ cC ] [ aA ] [ tT ]
* /
private static String buildCaseInsensitiveGlobPattern ( @NonNull String query ) {
2020-07-23 04:36:10 +02:00
if ( TextUtils . isEmpty ( query ) ) {
return "*" ;
}
StringBuilder pattern = new StringBuilder ( ) ;
for ( int i = 0 , len = query . codePointCount ( 0 , query . length ( ) ) ; i < len ; i + + ) {
String point = StringUtil . codePointToString ( query . codePointAt ( i ) ) ;
pattern . append ( "[" ) ;
pattern . append ( point . toLowerCase ( ) ) ;
pattern . append ( point . toUpperCase ( ) ) ;
pattern . append ( "]" ) ;
}
return "*" + pattern . toString ( ) + "*" ;
}
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 ;
2020-04-27 20:09:16 +02:00
String selection = REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
ID + " != ? AND " +
2019-12-18 06:44:21 +01:00
"(" + 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 ;
}
2020-06-12 19:22:46 +02:00
/ * *
* @param lastInteractionThreshold Only include contacts that have been interacted with since this time .
* @param lastProfileFetchThreshold Only include contacts that haven ' t their profile fetched after this time .
* @param limit Only return at most this many contact .
* /
public List < RecipientId > getRecipientsForRoutineProfileFetch ( long lastInteractionThreshold , long lastProfileFetchThreshold , int limit ) {
ThreadDatabase threadDatabase = DatabaseFactory . getThreadDatabase ( context ) ;
Set < Recipient > recipientsWithinInteractionThreshold = new LinkedHashSet < > ( ) ;
try ( ThreadDatabase . Reader reader = threadDatabase . readerFor ( threadDatabase . getRecentPushConversationList ( - 1 , false ) ) ) {
ThreadRecord record ;
while ( ( record = reader . getNext ( ) ) ! = null & & record . getDate ( ) > lastInteractionThreshold ) {
Recipient recipient = Recipient . resolved ( record . getRecipient ( ) . getId ( ) ) ;
if ( recipient . isGroup ( ) ) {
recipientsWithinInteractionThreshold . addAll ( recipient . getParticipants ( ) ) ;
} else {
recipientsWithinInteractionThreshold . add ( recipient ) ;
}
}
}
return Stream . of ( recipientsWithinInteractionThreshold )
. filterNot ( Recipient : : isLocalNumber )
. filter ( r - > r . getLastProfileFetchTime ( ) < lastProfileFetchThreshold )
. limit ( limit )
. map ( Recipient : : getId )
. toList ( ) ;
}
public void markProfilesFetched ( @NonNull Collection < RecipientId > ids , long time ) {
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
db . beginTransaction ( ) ;
try {
ContentValues values = new ContentValues ( 1 ) ;
values . put ( LAST_PROFILE_FETCH , time ) ;
for ( RecipientId id : ids ) {
db . update ( TABLE_NAME , values , ID_WHERE , new String [ ] { id . serialize ( ) } ) ;
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
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-04-20 17:01:31 +02:00
List < GroupId . V1 > groupIdStrings = Stream . of ( groupIds ) . map ( GroupId : : v1orThrow ) . 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 ( ) ;
}
2020-10-06 06:02:01 +02:00
for ( RecipientId id : keys . keySet ( ) ) {
Recipient . live ( id ) . refresh ( ) ;
}
2019-09-26 16:12:51 +02:00
}
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 ) {
2020-07-16 00:03:18 +02:00
Optional < RecipientId > remapped = RemappedRecords . getInstance ( ) . getRecipient ( context , id ) ;
if ( remapped . isPresent ( ) ) {
Log . w ( TAG , "While clearing dirty state, noticed we have a remapped contact (" + id + " to " + remapped . get ( ) + "). Safe to delete now." ) ;
db . delete ( TABLE_NAME , ID_WHERE , new String [ ] { id . serialize ( ) } ) ;
} else {
db . update ( TABLE_NAME , values , ID_WHERE , new String [ ] { id . serialize ( ) } ) ;
}
2019-09-26 16:12:51 +02:00
}
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
void markDirty ( @NonNull RecipientId recipientId , @NonNull DirtyState dirtyState ) {
2020-05-12 18:46:37 +02:00
Log . d ( TAG , "Attempting to mark " + recipientId + " with dirty state " + dirtyState ) ;
2020-04-23 19:33:48 +02:00
2019-09-26 16:12:51 +02:00
ContentValues contentValues = new ContentValues ( 1 ) ;
contentValues . put ( DIRTY , dirtyState . getId ( ) ) ;
2020-04-23 19:33:48 +02:00
String query = ID + " = ? AND (" + UUID + " NOT NULL OR " + PHONE + " NOT NULL OR " + GROUP_ID + " 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 ) {
2020-10-07 19:32:59 +02:00
SqlUtil . Query updateQuery = SqlUtil . buildTrueUpdateQuery ( ID_WHERE , SqlUtil . buildArgs ( id ) , 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 } .
* /
2020-10-07 19:32:59 +02:00
private boolean update ( @NonNull SqlUtil . Query updateQuery , @NonNull ContentValues contentValues ) {
2020-02-11 00:40:22 +01:00
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
}
}
}
2020-07-16 00:03:18 +02:00
/ * *
* Merges one UUID recipient with an E164 recipient . It is assumed that the E164 recipient does
* * not * have a UUID .
* /
@SuppressWarnings ( "ConstantConditions" )
private @NonNull RecipientId merge ( @NonNull RecipientId byUuid , @NonNull RecipientId byE164 ) {
ensureInTransaction ( ) ;
SQLiteDatabase db = databaseHelper . getWritableDatabase ( ) ;
RecipientSettings uuidSettings = getRecipientSettings ( byUuid ) ;
RecipientSettings e164Settings = getRecipientSettings ( byE164 ) ;
// Recipient
if ( e164Settings . getStorageId ( ) = = null ) {
Log . w ( TAG , "No storageId on the E164 recipient. Can delete right away." ) ;
db . delete ( TABLE_NAME , ID_WHERE , SqlUtil . buildArgs ( byE164 ) ) ;
} else {
Log . w ( TAG , "The E164 recipient has a storageId. Clearing data and marking for deletion." ) ;
ContentValues values = new ContentValues ( ) ;
values . putNull ( PHONE ) ;
values . put ( REGISTERED , RegisteredState . NOT_REGISTERED . getId ( ) ) ;
values . put ( DIRTY , DirtyState . DELETE . getId ( ) ) ;
db . update ( TABLE_NAME , values , ID_WHERE , SqlUtil . buildArgs ( byE164 ) ) ;
}
RemappedRecords . getInstance ( ) . addRecipient ( context , byE164 , byUuid ) ;
ContentValues uuidValues = new ContentValues ( ) ;
uuidValues . put ( PHONE , e164Settings . getE164 ( ) ) ;
uuidValues . put ( BLOCKED , e164Settings . isBlocked ( ) | | uuidSettings . isBlocked ( ) ) ;
uuidValues . put ( MESSAGE_RINGTONE , Optional . fromNullable ( uuidSettings . getMessageRingtone ( ) ) . or ( Optional . fromNullable ( e164Settings . getMessageRingtone ( ) ) ) . transform ( Uri : : toString ) . orNull ( ) ) ;
uuidValues . put ( MESSAGE_VIBRATE , uuidSettings . getMessageVibrateState ( ) ! = VibrateState . DEFAULT ? uuidSettings . getMessageVibrateState ( ) . getId ( ) : e164Settings . getMessageVibrateState ( ) . getId ( ) ) ;
uuidValues . put ( CALL_RINGTONE , Optional . fromNullable ( uuidSettings . getCallRingtone ( ) ) . or ( Optional . fromNullable ( e164Settings . getCallRingtone ( ) ) ) . transform ( Uri : : toString ) . orNull ( ) ) ;
uuidValues . put ( CALL_VIBRATE , uuidSettings . getCallVibrateState ( ) ! = VibrateState . DEFAULT ? uuidSettings . getCallVibrateState ( ) . getId ( ) : e164Settings . getCallVibrateState ( ) . getId ( ) ) ;
uuidValues . put ( NOTIFICATION_CHANNEL , uuidSettings . getNotificationChannel ( ) ! = null ? uuidSettings . getNotificationChannel ( ) : e164Settings . getNotificationChannel ( ) ) ;
uuidValues . put ( MUTE_UNTIL , uuidSettings . getMuteUntil ( ) > 0 ? uuidSettings . getMuteUntil ( ) : e164Settings . getMuteUntil ( ) ) ;
uuidValues . put ( COLOR , Optional . fromNullable ( uuidSettings . getColor ( ) ) . or ( Optional . fromNullable ( e164Settings . getColor ( ) ) ) . transform ( MaterialColor : : serialize ) . orNull ( ) ) ;
uuidValues . put ( SEEN_INVITE_REMINDER , e164Settings . getInsightsBannerTier ( ) . getId ( ) ) ;
uuidValues . put ( DEFAULT_SUBSCRIPTION_ID , e164Settings . getDefaultSubscriptionId ( ) . or ( - 1 ) ) ;
uuidValues . put ( MESSAGE_EXPIRATION_TIME , uuidSettings . getExpireMessages ( ) > 0 ? uuidSettings . getExpireMessages ( ) : e164Settings . getExpireMessages ( ) ) ;
uuidValues . put ( REGISTERED , RegisteredState . REGISTERED . getId ( ) ) ;
uuidValues . put ( SYSTEM_DISPLAY_NAME , e164Settings . getSystemDisplayName ( ) ) ;
uuidValues . put ( SYSTEM_PHOTO_URI , e164Settings . getSystemContactPhotoUri ( ) ) ;
uuidValues . put ( SYSTEM_PHONE_LABEL , e164Settings . getSystemPhoneLabel ( ) ) ;
uuidValues . put ( SYSTEM_CONTACT_URI , e164Settings . getSystemContactUri ( ) ) ;
uuidValues . put ( PROFILE_SHARING , uuidSettings . isProfileSharing ( ) | | e164Settings . isProfileSharing ( ) ) ;
uuidValues . put ( GROUPS_V2_CAPABILITY , uuidSettings . getGroupsV2Capability ( ) ! = Recipient . Capability . UNKNOWN ? uuidSettings . getGroupsV2Capability ( ) . serialize ( ) : e164Settings . getGroupsV2Capability ( ) . serialize ( ) ) ;
2020-08-13 15:54:33 +02:00
uuidValues . put ( MENTION_SETTING , uuidSettings . getMentionSetting ( ) ! = MentionSetting . ALWAYS_NOTIFY ? uuidSettings . getMentionSetting ( ) . getId ( ) : e164Settings . getMentionSetting ( ) . getId ( ) ) ;
2020-07-16 00:03:18 +02:00
if ( uuidSettings . getProfileKey ( ) ! = null ) {
updateProfileValuesForMerge ( uuidValues , uuidSettings ) ;
} else if ( e164Settings . getProfileKey ( ) ! = null ) {
updateProfileValuesForMerge ( uuidValues , e164Settings ) ;
}
db . update ( TABLE_NAME , uuidValues , ID_WHERE , SqlUtil . buildArgs ( byUuid ) ) ;
// Identities
db . delete ( IdentityDatabase . TABLE_NAME , IdentityDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
// Group Receipts
ContentValues groupReceiptValues = new ContentValues ( ) ;
groupReceiptValues . put ( GroupReceiptDatabase . RECIPIENT_ID , byUuid . serialize ( ) ) ;
db . update ( GroupReceiptDatabase . TABLE_NAME , groupReceiptValues , GroupReceiptDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
// Groups
GroupDatabase groupDatabase = DatabaseFactory . getGroupDatabase ( context ) ;
2020-08-06 22:23:25 +02:00
for ( GroupDatabase . GroupRecord group : groupDatabase . getGroupsContainingMember ( byE164 , false , true ) ) {
2020-07-16 00:03:18 +02:00
List < RecipientId > newMembers = new ArrayList < > ( group . getMembers ( ) ) ;
newMembers . remove ( byE164 ) ;
ContentValues groupValues = new ContentValues ( ) ;
groupValues . put ( GroupDatabase . MEMBERS , RecipientId . toSerializedList ( newMembers ) ) ;
db . update ( GroupDatabase . TABLE_NAME , groupValues , GroupDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( group . getRecipientId ( ) ) ) ;
}
// Threads
ThreadDatabase . MergeResult threadMerge = DatabaseFactory . getThreadDatabase ( context ) . merge ( byUuid , byE164 ) ;
// SMS Messages
ContentValues smsValues = new ContentValues ( ) ;
smsValues . put ( SmsDatabase . RECIPIENT_ID , byUuid . serialize ( ) ) ;
if ( threadMerge . neededMerge ) {
smsValues . put ( SmsDatabase . THREAD_ID , threadMerge . threadId ) ;
}
db . update ( SmsDatabase . TABLE_NAME , smsValues , SmsDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
// MMS Messages
ContentValues mmsValues = new ContentValues ( ) ;
mmsValues . put ( MmsDatabase . RECIPIENT_ID , byUuid . serialize ( ) ) ;
if ( threadMerge . neededMerge ) {
mmsValues . put ( MmsDatabase . THREAD_ID , threadMerge . threadId ) ;
}
db . update ( MmsDatabase . TABLE_NAME , mmsValues , MmsDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
// Sessions
boolean hasE164Session = DatabaseFactory . getSessionDatabase ( context ) . getAllFor ( byE164 ) . size ( ) > 0 ;
boolean hasUuidSession = DatabaseFactory . getSessionDatabase ( context ) . getAllFor ( byUuid ) . size ( ) > 0 ;
if ( hasE164Session & & hasUuidSession ) {
Log . w ( TAG , "Had a session for both users. Deleting the E164." ) ;
db . delete ( SessionDatabase . TABLE_NAME , SessionDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
} else if ( hasE164Session & & ! hasUuidSession ) {
Log . w ( TAG , "Had a session for E164, but not UUID. Re-assigning to the UUID." ) ;
ContentValues values = new ContentValues ( ) ;
values . put ( SessionDatabase . RECIPIENT_ID , byUuid . serialize ( ) ) ;
db . update ( SessionDatabase . TABLE_NAME , values , SessionDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
} else if ( ! hasE164Session & & hasUuidSession ) {
Log . w ( TAG , "Had a session for UUID, but not E164. No action necessary." ) ;
} else {
Log . w ( TAG , "Had no sessions. No action necessary." ) ;
}
2020-08-12 17:12:01 +02:00
// Mentions
ContentValues mentionRecipientValues = new ContentValues ( ) ;
mentionRecipientValues . put ( MentionDatabase . RECIPIENT_ID , byUuid . serialize ( ) ) ;
db . update ( MentionDatabase . TABLE_NAME , mentionRecipientValues , MentionDatabase . RECIPIENT_ID + " = ?" , SqlUtil . buildArgs ( byE164 ) ) ;
if ( threadMerge . neededMerge ) {
ContentValues mentionThreadValues = new ContentValues ( ) ;
mentionThreadValues . put ( MentionDatabase . THREAD_ID , threadMerge . threadId ) ;
db . update ( MentionDatabase . TABLE_NAME , mentionThreadValues , MentionDatabase . THREAD_ID + " = ?" , SqlUtil . buildArgs ( threadMerge . previousThreadId ) ) ;
}
2020-08-12 17:08:48 +02:00
DatabaseFactory . getThreadDatabase ( context ) . update ( threadMerge . threadId , false , false ) ;
2020-07-16 00:03:18 +02:00
return byUuid ;
}
private static void updateProfileValuesForMerge ( @NonNull ContentValues values , @NonNull RecipientSettings settings ) {
values . put ( PROFILE_KEY , settings . getProfileKey ( ) ! = null ? Base64 . encodeBytes ( settings . getProfileKey ( ) ) : null ) ;
values . put ( PROFILE_KEY_CREDENTIAL , settings . getProfileKeyCredential ( ) ! = null ? Base64 . encodeBytes ( settings . getProfileKeyCredential ( ) ) : null ) ;
values . put ( SIGNAL_PROFILE_AVATAR , settings . getProfileAvatar ( ) ) ;
values . put ( PROFILE_GIVEN_NAME , settings . getProfileName ( ) . getGivenName ( ) ) ;
values . put ( PROFILE_FAMILY_NAME , settings . getProfileName ( ) . getFamilyName ( ) ) ;
values . put ( PROFILE_JOINED_NAME , settings . getProfileName ( ) . toString ( ) ) ;
}
private void ensureInTransaction ( ) {
if ( ! databaseHelper . getWritableDatabase ( ) . inTransaction ( ) ) {
throw new IllegalStateException ( "Must be in a transaction!" ) ;
}
}
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 ) ;
2020-06-10 16:49:22 +02:00
boolean updatedValues = update ( id , refreshQualifyingValues ) ;
boolean updatedColor = displayName ! = null & & setColorIfNotSetInternal ( id , ContactColors . generateFor ( displayName ) ) ;
if ( updatedValues | | updatedColor ) {
2019-09-26 16:12:51 +02:00
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 + ", '')" ;
}
2020-08-05 22:45:52 +02:00
private static @NonNull String removeWhitespace ( @NonNull String column ) {
return "REPLACE(" + 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 ;
2020-04-22 16:13:56 +02:00
private final boolean hasProfileImage ;
2019-09-26 16:12:51 +02:00
private final boolean profileSharing ;
2020-06-12 19:22:46 +02:00
private final long lastProfileFetch ;
2019-09-26 16:12:51 +02:00
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 ;
2020-08-05 22:45:52 +02:00
private final MentionSetting mentionSetting ;
2020-10-06 16:24:14 +02:00
private final SyncExtras syncExtras ;
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 ,
2020-04-22 16:13:56 +02:00
boolean hasProfileImage ,
2018-08-16 18:47:43 +02:00
boolean profileSharing ,
2020-06-12 19:22:46 +02:00
long lastProfileFetch ,
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 ,
2020-08-20 22:05:50 +02:00
@NonNull MentionSetting mentionSetting ,
2020-10-06 16:24:14 +02:00
@NonNull SyncExtras syncExtras )
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 ;
2020-04-22 16:13:56 +02:00
this . hasProfileImage = hasProfileImage ;
2018-05-22 11:13:10 +02:00
this . profileSharing = profileSharing ;
2020-06-12 19:22:46 +02:00
this . lastProfileFetch = lastProfileFetch ;
2018-05-22 11:13:10 +02:00
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 ;
2020-08-05 22:45:52 +02:00
this . mentionSetting = mentionSetting ;
2020-10-06 16:24:14 +02:00
this . syncExtras = syncExtras ;
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
2020-04-22 16:13:56 +02:00
public boolean hasProfileImage ( ) {
return hasProfileImage ;
}
2017-08-17 06:49:41 +02:00
public boolean isProfileSharing ( ) {
return profileSharing ;
}
2018-08-16 18:47:43 +02:00
2020-06-12 19:22:46 +02:00
public long getLastProfileFetch ( ) {
return lastProfileFetch ;
}
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
}
2020-10-06 16:24:14 +02:00
public @NonNull MentionSetting getMentionSetting ( ) {
return mentionSetting ;
2019-09-26 16:12:51 +02:00
}
2020-10-06 16:24:14 +02:00
public @NonNull SyncExtras getSyncExtras ( ) {
return syncExtras ;
2019-09-26 16:12:51 +02:00
}
2020-08-05 22:45:52 +02:00
2020-10-06 16:24:14 +02:00
/ * *
* A bundle of data that ' s only necessary when syncing to storage service , not for a
* { @link Recipient } .
* /
public static class SyncExtras {
private final byte [ ] storageProto ;
private final GroupMasterKey groupMasterKey ;
private final byte [ ] identityKey ;
private final VerifiedStatus identityStatus ;
private final boolean archived ;
2020-10-07 19:32:59 +02:00
private final boolean forcedUnread ;
2020-10-06 16:24:14 +02:00
public SyncExtras ( @Nullable byte [ ] storageProto ,
@Nullable GroupMasterKey groupMasterKey ,
@Nullable byte [ ] identityKey ,
@NonNull VerifiedStatus identityStatus ,
2020-10-07 19:32:59 +02:00
boolean archived ,
boolean forcedUnread )
2020-10-06 16:24:14 +02:00
{
this . storageProto = storageProto ;
this . groupMasterKey = groupMasterKey ;
this . identityKey = identityKey ;
this . identityStatus = identityStatus ;
this . archived = archived ;
2020-10-07 19:32:59 +02:00
this . forcedUnread = forcedUnread ;
2020-10-06 16:24:14 +02:00
}
2020-08-20 22:05:50 +02:00
2020-10-06 16:24:14 +02:00
public @Nullable byte [ ] getStorageProto ( ) {
return storageProto ;
}
public @Nullable GroupMasterKey getGroupMasterKey ( ) {
return groupMasterKey ;
}
public boolean isArchived ( ) {
return archived ;
}
public @Nullable byte [ ] getIdentityKey ( ) {
return identityKey ;
}
public @NonNull VerifiedStatus getIdentityStatus ( ) {
return identityStatus ;
}
2020-10-07 19:32:59 +02:00
public boolean isForcedUnread ( ) {
return forcedUnread ;
}
2020-08-20 22:05:50 +02:00
}
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
2020-07-16 00:03:18 +02:00
public final static class RecipientIdResult {
private final RecipientId recipientId ;
private final boolean requiresDirectoryRefresh ;
public RecipientIdResult ( @NonNull RecipientId recipientId , boolean requiresDirectoryRefresh ) {
this . recipientId = recipientId ;
this . requiresDirectoryRefresh = requiresDirectoryRefresh ;
}
public @NonNull RecipientId getRecipientId ( ) {
return recipientId ;
}
public boolean requiresDirectoryRefresh ( ) {
return requiresDirectoryRefresh ;
}
}
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 ;
}
}
2020-05-04 06:48:09 +02:00
public static class MissingRecipientException extends IllegalStateException {
public MissingRecipientException ( @Nullable RecipientId id ) {
2019-10-02 18:06:13 +02:00
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
}