Respect the phoneNumberSharing setting on the profile.
This commit is contained in:
parent
624f863da4
commit
bb30535afb
24 changed files with 257 additions and 44 deletions
|
@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||
|
@ -45,6 +46,7 @@ class PhoneNumberPrivacySettingsViewModel : ViewModel() {
|
|||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = if (phoneNumberSharingEnabled) PhoneNumberSharingMode.EVERYBODY else PhoneNumberSharingMode.NOBODY
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
ApplicationDependencies.getJobManager().add(ProfileUploadJob())
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,11 @@ object BioTextPreference {
|
|||
}
|
||||
}
|
||||
|
||||
override fun getSubhead2Text(): String? = recipient.e164.map(PhoneNumberFormatter::prettyPrint).orElse(null)
|
||||
override fun getSubhead2Text(): String? = if (recipient.shouldShowE164()) {
|
||||
recipient.e164.map(PhoneNumberFormatter::prettyPrint).orElse(null)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: RecipientModel): Boolean {
|
||||
return super.areContentsTheSame(newItem) && newItem.recipient.hasSameContent(recipient)
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
|
|||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
@ -437,8 +436,7 @@ open class ContactSearchAdapter(
|
|||
number.text = recipient.combinedAboutAndEmoji
|
||||
number.visible = true
|
||||
} else if (displayOptions.displaySecondaryInformation == DisplaySecondaryInformation.ALWAYS && recipient.hasE164()) {
|
||||
number.text = PhoneNumberFormatter.prettyPrint(recipient.requireE164())
|
||||
number.visible = true
|
||||
number.visible = false
|
||||
} else {
|
||||
super.bindNumberField(model)
|
||||
}
|
||||
|
|
|
@ -581,7 +581,7 @@ class ConversationAdapterV2(
|
|||
} else if (isSelf) {
|
||||
conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation))
|
||||
} else {
|
||||
val subtitle: String? = recipient.e164.map { e164: String? -> PhoneNumberFormatter.prettyPrint(e164!!) }.orElse(null)
|
||||
val subtitle: String? = recipient.takeIf { it.shouldShowE164() }?.e164?.map { e164: String? -> PhoneNumberFormatter.prettyPrint(e164!!) }?.orElse(null)
|
||||
if (subtitle == null || subtitle == title) {
|
||||
conversationBanner.hideSubtitle()
|
||||
} else {
|
||||
|
|
|
@ -177,6 +177,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
const val BADGES = "badges"
|
||||
const val NEEDS_PNI_SIGNATURE = "needs_pni_signature"
|
||||
const val REPORTING_TOKEN = "reporting_token"
|
||||
const val PHONE_NUMBER_SHARING = "phone_number_sharing"
|
||||
|
||||
const val SEARCH_PROFILE_NAME = "search_signal_profile"
|
||||
const val SORT_NAME = "sort_name"
|
||||
|
@ -242,7 +243,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
$CUSTOM_CHAT_COLORS_ID INTEGER DEFAULT 0,
|
||||
$BADGES BLOB DEFAULT NULL,
|
||||
$NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0,
|
||||
$REPORTING_TOKEN BLOB DEFAULT NULL
|
||||
$REPORTING_TOKEN BLOB DEFAULT NULL,
|
||||
$PHONE_NUMBER_SHARING INTEGER DEFAULT ${PhoneNumberSharingState.UNKNOWN.id}
|
||||
)
|
||||
"""
|
||||
|
||||
|
@ -301,7 +303,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
CUSTOM_CHAT_COLORS_ID,
|
||||
BADGES,
|
||||
NEEDS_PNI_SIGNATURE,
|
||||
REPORTING_TOKEN
|
||||
REPORTING_TOKEN,
|
||||
PHONE_NUMBER_SHARING
|
||||
)
|
||||
|
||||
private val ID_PROJECTION = arrayOf(ID)
|
||||
|
@ -1804,6 +1807,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
}
|
||||
}
|
||||
|
||||
fun setPhoneNumberSharing(id: RecipientId, phoneNumberSharing: PhoneNumberSharingState) {
|
||||
val contentValues = contentValuesOf(
|
||||
PHONE_NUMBER_SHARING to phoneNumberSharing.id
|
||||
)
|
||||
if (update(id, contentValues)) {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetAllWallpaper() {
|
||||
val database = writableDatabase
|
||||
val selection = SqlUtil.buildArgs(ID, WALLPAPER_URI)
|
||||
|
@ -3339,7 +3351,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
(
|
||||
$SORT_NAME GLOB ? OR
|
||||
$USERNAME GLOB ? OR
|
||||
$E164 GLOB ? OR
|
||||
${ContactSearchSelection.E164_SEARCH} OR
|
||||
$EMAIL GLOB ?
|
||||
)
|
||||
"""
|
||||
|
@ -3360,7 +3372,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
(
|
||||
$SORT_NAME GLOB ? OR
|
||||
$USERNAME GLOB ? OR
|
||||
$E164 GLOB ? OR
|
||||
${ContactSearchSelection.E164_SEARCH} OR
|
||||
$EMAIL GLOB ?
|
||||
))
|
||||
"""
|
||||
|
@ -3381,7 +3393,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
AND (
|
||||
$SORT_NAME GLOB ? OR
|
||||
$USERNAME GLOB ? OR
|
||||
$E164 GLOB ? OR
|
||||
${ContactSearchSelection.E164_SEARCH} OR
|
||||
$EMAIL GLOB ?
|
||||
)
|
||||
"""
|
||||
|
@ -4401,16 +4413,17 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
WHERE ${GroupTable.MembershipTable.TABLE_NAME}.${GroupTable.MembershipTable.RECIPIENT_ID} = $TABLE_NAME.$ID AND ${GroupTable.TABLE_NAME}.${GroupTable.ACTIVE} = 1 AND ${GroupTable.TABLE_NAME}.${GroupTable.MMS} = 0
|
||||
)
|
||||
"""
|
||||
val E164_SEARCH = "(($PHONE_NUMBER_SHARING != ${PhoneNumberSharingState.DISABLED.id} OR $SYSTEM_CONTACT_URI NOT NULL) AND $E164 GLOB ?)"
|
||||
const val FILTER_GROUPS = " AND $GROUP_ID IS NULL"
|
||||
const val FILTER_ID = " AND $ID != ?"
|
||||
const val FILTER_BLOCKED = " AND $BLOCKED = ?"
|
||||
const val FILTER_HIDDEN = " AND $HIDDEN = ?"
|
||||
const val NON_SIGNAL_CONTACT = "$REGISTERED != ? AND $SYSTEM_CONTACT_URI NOT NULL AND ($E164 NOT NULL OR $EMAIL NOT NULL)"
|
||||
const val QUERY_NON_SIGNAL_CONTACT = "$NON_SIGNAL_CONTACT AND ($E164 GLOB ? OR $EMAIL GLOB ? OR $SYSTEM_JOINED_NAME GLOB ?)"
|
||||
val QUERY_NON_SIGNAL_CONTACT = "$NON_SIGNAL_CONTACT AND ($E164_SEARCH OR $EMAIL GLOB ? OR $SYSTEM_JOINED_NAME GLOB ?)"
|
||||
const val SIGNAL_CONTACT = "$REGISTERED = ? AND (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)"
|
||||
const val QUERY_SIGNAL_CONTACT = "$SIGNAL_CONTACT AND ($E164 GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
|
||||
val QUERY_SIGNAL_CONTACT = "$SIGNAL_CONTACT AND ($E164_SEARCH OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
|
||||
val GROUP_MEMBER_CONTACT = "$REGISTERED = ? AND $HAS_GROUP_IN_COMMON AND NOT (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)"
|
||||
val QUERY_GROUP_MEMBER_CONTACT = "$GROUP_MEMBER_CONTACT AND ($E164 GLOB ? OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
|
||||
val QUERY_GROUP_MEMBER_CONTACT = "$GROUP_MEMBER_CONTACT AND ($E164_SEARCH OR $SORT_NAME GLOB ? OR $USERNAME GLOB ?)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4500,6 +4513,19 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
}
|
||||
}
|
||||
|
||||
enum class PhoneNumberSharingState(val id: Int) {
|
||||
UNKNOWN(0), ENABLED(1), DISABLED(2);
|
||||
|
||||
val enabled
|
||||
get() = this == ENABLED || this == UNKNOWN
|
||||
|
||||
companion object {
|
||||
fun fromId(id: Int): PhoneNumberSharingState {
|
||||
return values()[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CdsV2Result(
|
||||
val pni: PNI,
|
||||
val aci: ACI?
|
||||
|
|
|
@ -164,7 +164,8 @@ object RecipientTableCursorUtil {
|
|||
badges = parseBadgeList(cursor.requireBlob(RecipientTable.BADGES)),
|
||||
needsPniSignature = cursor.requireBoolean(RecipientTable.NEEDS_PNI_SIGNATURE),
|
||||
hiddenState = Recipient.HiddenState.deserialize(cursor.requireInt(RecipientTable.HIDDEN)),
|
||||
callLinkRoomId = cursor.requireString(RecipientTable.CALL_LINK_ROOM_ID)?.let { CallLinkRoomId.DatabaseSerializer.deserialize(it) }
|
||||
callLinkRoomId = cursor.requireString(RecipientTable.CALL_LINK_ROOM_ID)?.let { CallLinkRoomId.DatabaseSerializer.deserialize(it) },
|
||||
phoneNumberSharing = cursor.requireInt(RecipientTable.PHONE_NUMBER_SHARING).let { RecipientTable.PhoneNumberSharingState.fromId(it) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V210_FixPniPossible
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V211_ReceiptColumnRenames
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V212_RemoveDistributionListUniqueConstraint
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V213_FixUsernameInE164Column
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V214_PhoneNumberSharingColumn
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -144,10 +145,11 @@ object SignalDatabaseMigrations {
|
|||
210 to V210_FixPniPossibleColumns,
|
||||
211 to V211_ReceiptColumnRenames,
|
||||
212 to V212_RemoveDistributionListUniqueConstraint,
|
||||
213 to V213_FixUsernameInE164Column
|
||||
213 to V213_FixUsernameInE164Column,
|
||||
214 to V214_PhoneNumberSharingColumn
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 213
|
||||
const val DATABASE_VERSION = 214
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds a phone_number_sharing column to the recipient table.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V214_PhoneNumberSharingColumn : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN phone_number_sharing INTEGER DEFAULT 0")
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
|||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
|
@ -76,7 +77,8 @@ data class RecipientRecord(
|
|||
@get:JvmName("needsPniSignature")
|
||||
val needsPniSignature: Boolean,
|
||||
val hiddenState: Recipient.HiddenState,
|
||||
val callLinkRoomId: CallLinkRoomId?
|
||||
val callLinkRoomId: CallLinkRoomId?,
|
||||
val phoneNumberSharing: PhoneNumberSharingState
|
||||
) {
|
||||
|
||||
fun e164Only(): Boolean {
|
||||
|
|
|
@ -148,6 +148,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
|||
setProfileCapabilities(profile.getCapabilities());
|
||||
setProfileBadges(profile.getBadges());
|
||||
ensureUnidentifiedAccessCorrect(profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
||||
ensurePhoneNumberSharingIsCorrect(profile.getPhoneNumberSharing());
|
||||
|
||||
profileAndCredential.getExpiringProfileKeyCredential()
|
||||
.ifPresent(expiringProfileKeyCredential -> setExpiringProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), expiringProfileKeyCredential));
|
||||
|
@ -256,6 +257,45 @@ public class RefreshOwnProfileJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to make sure that our phone number sharing setting matches what's on our profile. If there's a mismatch, we first sync with storage service
|
||||
* (to limit race conditions between devices) and then upload our profile.
|
||||
*/
|
||||
private void ensurePhoneNumberSharingIsCorrect(@Nullable String phoneNumberSharingCiphertext) {
|
||||
if (phoneNumberSharingCiphertext == null) {
|
||||
Log.w(TAG, "No phone number sharing is set remotely! Syncing with storage service, then uploading our profile.");
|
||||
syncWithStorageServiceThenUploadProfile();
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
ProfileCipher cipher = new ProfileCipher(profileKey);
|
||||
|
||||
try {
|
||||
RecipientTable.PhoneNumberSharingState remotePhoneNumberSharing = cipher.decryptBoolean(Base64.decode(phoneNumberSharingCiphertext))
|
||||
.map(value -> value ? RecipientTable.PhoneNumberSharingState.ENABLED : RecipientTable.PhoneNumberSharingState.DISABLED)
|
||||
.orElse(RecipientTable.PhoneNumberSharingState.UNKNOWN);
|
||||
|
||||
if (remotePhoneNumberSharing == RecipientTable.PhoneNumberSharingState.UNKNOWN || remotePhoneNumberSharing.getEnabled() != SignalStore.phoneNumberPrivacy().isPhoneNumberSharingEnabled()) {
|
||||
Log.w(TAG, "Phone number sharing setting did not match! Syncing with storage service, then uploading our profile.");
|
||||
syncWithStorageServiceThenUploadProfile();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to decode phone number sharing! Syncing with storage service, then uploading our profile.", e);
|
||||
syncWithStorageServiceThenUploadProfile();
|
||||
} catch (InvalidCiphertextException e) {
|
||||
Log.w(TAG, "Failed to decrypt phone number sharing! Syncing with storage service, then uploading our profile.", e);
|
||||
syncWithStorageServiceThenUploadProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private void syncWithStorageServiceThenUploadProfile() {
|
||||
ApplicationDependencies.getJobManager()
|
||||
.startChain(new StorageSyncJob())
|
||||
.then(new ProfileUploadJob())
|
||||
.enqueue();
|
||||
}
|
||||
|
||||
static void checkUsernameIsInSync() {
|
||||
boolean validated = false;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.signal.core.util.logging.Log;
|
|||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.badges.Badges;
|
||||
|
@ -28,6 +29,7 @@ import org.thoughtcrime.securesms.badges.models.Badge;
|
|||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
|
@ -411,6 +413,14 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
return true;
|
||||
}
|
||||
|
||||
PhoneNumberSharingState remotePhoneNumberSharing = ProfileUtil.decryptBoolean(profileKey, remoteProfile.getPhoneNumberSharing())
|
||||
.map(value -> value ? PhoneNumberSharingState.ENABLED : PhoneNumberSharingState.DISABLED)
|
||||
.orElse(PhoneNumberSharingState.UNKNOWN);
|
||||
|
||||
if (localRecipientRecord.getPhoneNumberSharing() != remotePhoneNumberSharing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -425,6 +435,7 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
setProfileBadges(recipient, profile.getBadges());
|
||||
setProfileCapabilities(recipient, profile.getCapabilities());
|
||||
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
||||
setPhoneNumberSharingMode(recipient, profile.getPhoneNumberSharing());
|
||||
|
||||
if (recipientProfileKey != null) {
|
||||
profileAndCredential.getExpiringProfileKeyCredential()
|
||||
|
@ -611,6 +622,26 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
SignalDatabase.recipients().setCapabilities(recipient.getId(), capabilities);
|
||||
}
|
||||
|
||||
private void setPhoneNumberSharingMode(Recipient recipient, String phoneNumberSharing) {
|
||||
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
if (profileKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PhoneNumberSharingState remotePhoneNumberSharing = ProfileUtil.decryptBoolean(profileKey, phoneNumberSharing)
|
||||
.map(value -> value ? PhoneNumberSharingState.ENABLED : PhoneNumberSharingState.DISABLED)
|
||||
.orElse(PhoneNumberSharingState.UNKNOWN);
|
||||
|
||||
if (recipient.getPhoneNumberSharing() != remotePhoneNumberSharing) {
|
||||
Log.i(TAG, "Updating phone number sharing state for " + recipient.getId() + " to " + remotePhoneNumberSharing);
|
||||
SignalDatabase.recipients().setPhoneNumberSharing(recipient.getId(), remotePhoneNumberSharing);
|
||||
}
|
||||
} catch (InvalidCiphertextException | IOException e) {
|
||||
Log.w(TAG, "Failed to set the phone number sharing setting!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collective state as responses are processed as they come in.
|
||||
*/
|
||||
|
|
|
@ -2,9 +2,11 @@ package org.thoughtcrime.securesms.profiles.edit.pnp
|
|||
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
|
||||
/**
|
||||
* Manages the current phone-number listing state.
|
||||
|
@ -31,6 +33,8 @@ class WhoCanFindMeByPhoneNumberRepository {
|
|||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
ApplicationDependencies.getJobManager().add(ProfileUploadJob())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
|||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState;
|
||||
|
@ -137,6 +138,7 @@ public class Recipient {
|
|||
private final boolean needsPniSignature;
|
||||
private final CallLinkRoomId callLinkRoomId;
|
||||
private final Optional<GroupRecord> groupRecord;
|
||||
private final PhoneNumberSharingState phoneNumberSharing;
|
||||
|
||||
/**
|
||||
* Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be
|
||||
|
@ -426,6 +428,7 @@ public class Recipient {
|
|||
this.isActiveGroup = false;
|
||||
this.callLinkRoomId = null;
|
||||
this.groupRecord = Optional.empty();
|
||||
this.phoneNumberSharing = PhoneNumberSharingState.UNKNOWN;
|
||||
}
|
||||
|
||||
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
|
||||
|
@ -481,6 +484,7 @@ public class Recipient {
|
|||
this.isActiveGroup = details.isActiveGroup;
|
||||
this.callLinkRoomId = details.callLinkRoomId;
|
||||
this.groupRecord = details.groupRecord;
|
||||
this.phoneNumberSharing = details.phoneNumberSharing;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
|
@ -679,6 +683,13 @@ public class Recipient {
|
|||
return Optional.ofNullable(e164);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should show this user's e164 in the interface.
|
||||
*/
|
||||
public boolean shouldShowE164() {
|
||||
return hasE164() && (isSystemContact() || getPhoneNumberSharing() != PhoneNumberSharingState.DISABLED);
|
||||
}
|
||||
|
||||
public @NonNull Optional<String> getEmail() {
|
||||
return Optional.ofNullable(email);
|
||||
}
|
||||
|
@ -1230,6 +1241,10 @@ public class Recipient {
|
|||
return Objects.requireNonNull(callLinkRoomId);
|
||||
}
|
||||
|
||||
public PhoneNumberSharingState getPhoneNumberSharing() {
|
||||
return phoneNumberSharing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -1386,7 +1401,8 @@ public class Recipient {
|
|||
hasGroupsInCommon == other.hasGroupsInCommon &&
|
||||
Objects.equals(badges, other.badges) &&
|
||||
isActiveGroup == other.isActiveGroup &&
|
||||
Objects.equals(callLinkRoomId, other.callLinkRoomId);
|
||||
Objects.equals(callLinkRoomId, other.callLinkRoomId) &&
|
||||
phoneNumberSharing == other.phoneNumberSharing;
|
||||
}
|
||||
|
||||
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.badges.models.Badge
|
|||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
|
@ -79,7 +80,8 @@ class RecipientDetails private constructor(
|
|||
@JvmField val isReleaseChannel: Boolean,
|
||||
@JvmField val needsPniSignature: Boolean,
|
||||
@JvmField val callLinkRoomId: CallLinkRoomId?,
|
||||
@JvmField val groupRecord: Optional<GroupRecord>
|
||||
@JvmField val groupRecord: Optional<GroupRecord>,
|
||||
@JvmField val phoneNumberSharing: PhoneNumberSharingState
|
||||
) {
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -143,7 +145,8 @@ class RecipientDetails private constructor(
|
|||
isReleaseChannel = isReleaseChannel,
|
||||
needsPniSignature = record.needsPniSignature,
|
||||
callLinkRoomId = record.callLinkRoomId,
|
||||
groupRecord = groupRecord
|
||||
groupRecord = groupRecord,
|
||||
phoneNumberSharing = record.phoneNumberSharing
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -271,7 +274,8 @@ class RecipientDetails private constructor(
|
|||
needsPniSignature = false,
|
||||
isActiveGroup = false,
|
||||
callLinkRoomId = null,
|
||||
groupRecord = Optional.empty()
|
||||
groupRecord = Optional.empty(),
|
||||
phoneNumberSharing = PhoneNumberSharingState.UNKNOWN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public final class RecipientExporter {
|
|||
}
|
||||
|
||||
private static void addAddressToIntent(Intent intent, Recipient recipient) {
|
||||
if (recipient.getE164().isPresent()) {
|
||||
if (recipient.getE164().isPresent() && recipient.shouldShowE164()) {
|
||||
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.requireE164());
|
||||
} else if (recipient.getEmail().isPresent()) {
|
||||
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.requireEmail());
|
||||
|
|
|
@ -154,7 +154,7 @@ private fun AboutSheetContent(
|
|||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (recipient.about != null) {
|
||||
if (!recipient.about.isNullOrBlank()) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_edit_24),
|
||||
text = {
|
||||
|
@ -190,7 +190,7 @@ private fun AboutSheetContent(
|
|||
)
|
||||
}
|
||||
|
||||
if (recipient.e164.isPresent) {
|
||||
if (recipient.e164.isPresent && recipient.shouldShowE164()) {
|
||||
val e164 = remember(recipient.e164.get()) {
|
||||
PhoneNumberFormatter.get(context).prettyPrintFormat(recipient.e164.get())
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
|||
about.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String usernameNumberString = recipient.hasAUserSetDisplayName(requireContext()) && !recipient.isSelf()
|
||||
String usernameNumberString = recipient.hasAUserSetDisplayName(requireContext()) && !recipient.isSelf() && recipient.shouldShowE164()
|
||||
? recipient.getSmsAddress().map(PhoneNumberFormatter::prettyPrint).orElse("").trim()
|
||||
: "";
|
||||
usernameNumber.setText(usernameNumberString);
|
||||
|
|
|
@ -150,6 +150,17 @@ public final class ProfileUtil {
|
|||
return decryptString(profileKey, Base64.decode(encryptedStringBase64));
|
||||
}
|
||||
|
||||
public static Optional<Boolean> decryptBoolean(@NonNull ProfileKey profileKey, @Nullable String encryptedBooleanBase64)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
if (encryptedBooleanBase64 == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
return profileCipher.decryptBoolean(Base64.decode(encryptedBooleanBase64));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull MobileCoinPublicAddress getAddressForRecipient(@NonNull Recipient recipient)
|
||||
throws IOException, PaymentsAddressException
|
||||
|
@ -347,7 +358,8 @@ public final class ProfileUtil {
|
|||
aboutEmoji,
|
||||
Optional.ofNullable(paymentsAddress),
|
||||
avatar,
|
||||
badgeIds).orElse(null);
|
||||
badgeIds,
|
||||
SignalStore.phoneNumberPrivacy().isPhoneNumberSharingEnabled()).orElse(null);
|
||||
SignalStore.registrationValues().markHasUploadedProfile();
|
||||
if (!avatar.keepTheSame) {
|
||||
SignalDatabase.recipients().setProfileAvatar(Recipient.self().getId(), avatarPath);
|
||||
|
|
|
@ -146,7 +146,8 @@ object RecipientDatabaseTestUtils {
|
|||
badges = badges,
|
||||
needsPniSignature = false,
|
||||
hiddenState = Recipient.HiddenState.NOT_HIDDEN,
|
||||
callLinkRoomId = null
|
||||
callLinkRoomId = null,
|
||||
phoneNumberSharing = RecipientTable.PhoneNumberSharingState.UNKNOWN
|
||||
),
|
||||
participantIds = participants,
|
||||
isReleaseChannel = isReleaseChannel,
|
||||
|
|
|
@ -37,6 +37,19 @@ public final class RecipientExporterTest {
|
|||
assertNull(intent.getStringExtra(EMAIL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asAddContactIntent_with_phone_number_should_not_show_number() {
|
||||
Recipient recipient = givenPhoneRecipient(ProfileName.fromParts("Alice", null), "+1555123456", false);
|
||||
|
||||
Intent intent = RecipientExporter.export(recipient).asAddContactIntent();
|
||||
|
||||
assertEquals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction());
|
||||
assertEquals(ContactsContract.Contacts.CONTENT_ITEM_TYPE, intent.getType());
|
||||
assertEquals("Alice", intent.getStringExtra(NAME));
|
||||
assertNull(intent.getStringExtra(PHONE));
|
||||
assertNull(intent.getStringExtra(EMAIL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asAddContactIntent_with_email() {
|
||||
Recipient recipient = givenEmailRecipient(ProfileName.fromParts("Bob", null), "bob@signal.org");
|
||||
|
@ -50,13 +63,19 @@ public final class RecipientExporterTest {
|
|||
assertNull(intent.getStringExtra(PHONE));
|
||||
}
|
||||
|
||||
|
||||
private Recipient givenPhoneRecipient(ProfileName profileName, String phone) {
|
||||
return givenPhoneRecipient(profileName, phone, true);
|
||||
}
|
||||
|
||||
private Recipient givenPhoneRecipient(ProfileName profileName, String phone, boolean shouldShowPhoneNumber) {
|
||||
Recipient recipient = mock(Recipient.class);
|
||||
when(recipient.getProfileName()).thenReturn(profileName);
|
||||
|
||||
when(recipient.requireE164()).thenReturn(phone);
|
||||
when(recipient.getE164()).thenAnswer(i -> Optional.of(phone));
|
||||
when(recipient.getEmail()).thenAnswer(i -> Optional.empty());
|
||||
when(recipient.shouldShowE164()).thenAnswer(i -> shouldShowPhoneNumber);
|
||||
|
||||
return recipient;
|
||||
}
|
||||
|
|
|
@ -722,17 +722,19 @@ public class SignalServiceAccountManager {
|
|||
String aboutEmoji,
|
||||
Optional<PaymentAddress> paymentsAddress,
|
||||
AvatarUploadParams avatar,
|
||||
List<String> visibleBadgeIds)
|
||||
List<String> visibleBadgeIds,
|
||||
boolean phoneNumberSharing)
|
||||
throws IOException
|
||||
{
|
||||
if (name == null) name = "";
|
||||
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
byte[] ciphertextName = profileCipher.encryptString(name, ProfileCipher.getTargetNameLength(name));
|
||||
byte[] ciphertextAbout = profileCipher.encryptString(about, ProfileCipher.getTargetAboutLength(about));
|
||||
byte[] ciphertextEmoji = profileCipher.encryptString(aboutEmoji, ProfileCipher.EMOJI_PADDED_LENGTH);
|
||||
byte[] ciphertextMobileCoinAddress = paymentsAddress.map(address -> profileCipher.encryptWithLength(address.encode(), ProfileCipher.PAYMENTS_ADDRESS_CONTENT_SIZE)).orElse(null);
|
||||
ProfileAvatarData profileAvatarData = null;
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
byte[] ciphertextName = profileCipher.encryptString(name, ProfileCipher.getTargetNameLength(name));
|
||||
byte[] ciphertextAbout = profileCipher.encryptString(about, ProfileCipher.getTargetAboutLength(about));
|
||||
byte[] ciphertextEmoji = profileCipher.encryptString(aboutEmoji, ProfileCipher.EMOJI_PADDED_LENGTH);
|
||||
byte[] ciphertextMobileCoinAddress = paymentsAddress.map(address -> profileCipher.encryptWithLength(address.encode(), ProfileCipher.PAYMENTS_ADDRESS_CONTENT_SIZE)).orElse(null);
|
||||
byte[] cipherTextPhoneNumberSharing = profileCipher.encryptBoolean(phoneNumberSharing);
|
||||
ProfileAvatarData profileAvatarData = null;
|
||||
|
||||
if (avatar.stream != null && !avatar.keepTheSame) {
|
||||
profileAvatarData = new ProfileAvatarData(avatar.stream.getStream(),
|
||||
|
@ -746,6 +748,7 @@ public class SignalServiceAccountManager {
|
|||
ciphertextAbout,
|
||||
ciphertextEmoji,
|
||||
ciphertextMobileCoinAddress,
|
||||
cipherTextPhoneNumberSharing,
|
||||
avatar.hasAvatar,
|
||||
avatar.keepTheSame,
|
||||
profileKey.getCommitment(aci.getLibSignalAci()).serialize(),
|
||||
|
|
|
@ -14,7 +14,9 @@ import java.security.InvalidKeyException;
|
|||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
@ -127,6 +129,22 @@ public class ProfileCipher {
|
|||
return new String(plaintext);
|
||||
}
|
||||
|
||||
public byte[] encryptBoolean(boolean input) {
|
||||
byte[] value = new byte[1];
|
||||
value[0] = (byte) (input ? 1 : 0);
|
||||
|
||||
return encrypt(value, value.length);
|
||||
}
|
||||
|
||||
public Optional<Boolean> decryptBoolean(@Nullable byte[] input) throws InvalidCiphertextException {
|
||||
if (input == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
byte[] paddedPlaintext = decrypt(input);
|
||||
return Optional.of(paddedPlaintext[0] != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the length, and adds padding.
|
||||
* <p>
|
||||
|
|
|
@ -63,6 +63,9 @@ public class SignalServiceProfile {
|
|||
@JsonProperty
|
||||
private List<Badge> badges;
|
||||
|
||||
@JsonProperty
|
||||
private String phoneNumberSharing;
|
||||
|
||||
@JsonIgnore
|
||||
private RequestType requestType;
|
||||
|
||||
|
@ -96,6 +99,10 @@ public class SignalServiceProfile {
|
|||
return unidentifiedAccess;
|
||||
}
|
||||
|
||||
public String getPhoneNumberSharing() {
|
||||
return phoneNumberSharing;
|
||||
}
|
||||
|
||||
public boolean isUnrestrictedUnidentifiedAccess() {
|
||||
return unrestrictedUnidentifiedAccess;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ public class SignalServiceProfileWrite {
|
|||
@JsonProperty
|
||||
private byte[] paymentAddress;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] phoneNumberSharing;
|
||||
|
||||
@JsonProperty
|
||||
private boolean avatar;
|
||||
|
||||
|
@ -38,16 +41,17 @@ public class SignalServiceProfileWrite {
|
|||
public SignalServiceProfileWrite(){
|
||||
}
|
||||
|
||||
public SignalServiceProfileWrite(String version, byte[] name, byte[] about, byte[] aboutEmoji, byte[] paymentAddress, boolean hasAvatar, boolean sameAvatar, byte[] commitment, List<String> badgeIds) {
|
||||
this.version = version;
|
||||
this.name = name;
|
||||
this.about = about;
|
||||
this.aboutEmoji = aboutEmoji;
|
||||
this.paymentAddress = paymentAddress;
|
||||
this.avatar = hasAvatar;
|
||||
this.sameAvatar = sameAvatar;
|
||||
this.commitment = commitment;
|
||||
this.badgeIds = badgeIds;
|
||||
public SignalServiceProfileWrite(String version, byte[] name, byte[] about, byte[] aboutEmoji, byte[] paymentAddress, byte[] phoneNumberSharing, boolean hasAvatar, boolean sameAvatar, byte[] commitment, List<String> badgeIds) {
|
||||
this.version = version;
|
||||
this.name = name;
|
||||
this.about = about;
|
||||
this.aboutEmoji = aboutEmoji;
|
||||
this.paymentAddress = paymentAddress;
|
||||
this.phoneNumberSharing = phoneNumberSharing;
|
||||
this.avatar = hasAvatar;
|
||||
this.sameAvatar = sameAvatar;
|
||||
this.commitment = commitment;
|
||||
this.badgeIds = badgeIds;
|
||||
}
|
||||
|
||||
public boolean hasAvatar() {
|
||||
|
|
Loading…
Add table
Reference in a new issue