Phone number privacy settings and certificate support behind feature flag.
This commit is contained in:
parent
abd3d4b546
commit
7b24e66ed3
23 changed files with 520 additions and 144 deletions
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
@ -10,6 +11,9 @@ import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||||
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.CertificateType;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
@ -44,21 +48,17 @@ public class UnidentifiedAccessUtil {
|
||||||
try {
|
try {
|
||||||
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
|
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
|
||||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||||
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
|
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(recipient);
|
||||||
|
|
||||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||||
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
|
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) +
|
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) +
|
||||||
" | Our access key present? " + (ourUnidentifiedAccessKey != null) +
|
|
||||||
" | Our certificate present? " + (ourUnidentifiedAccessCertificate != null) +
|
" | Our certificate present? " + (ourUnidentifiedAccessCertificate != null) +
|
||||||
" | UUID certificate supported? " + recipient.isUuidSupported());
|
" | UUID certificate supported? " + recipient.isUuidSupported());
|
||||||
|
|
||||||
if (theirUnidentifiedAccessKey != null &&
|
if (theirUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
|
||||||
ourUnidentifiedAccessKey != null &&
|
|
||||||
ourUnidentifiedAccessCertificate != null)
|
|
||||||
{
|
|
||||||
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey,
|
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey,
|
||||||
ourUnidentifiedAccessCertificate),
|
ourUnidentifiedAccessCertificate),
|
||||||
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||||
|
@ -75,13 +75,13 @@ public class UnidentifiedAccessUtil {
|
||||||
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
|
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
|
||||||
try {
|
try {
|
||||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||||
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
|
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(Recipient.self());
|
||||||
|
|
||||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||||
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
|
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
|
if (ourUnidentifiedAccessCertificate != null) {
|
||||||
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||||
ourUnidentifiedAccessCertificate),
|
ourUnidentifiedAccessCertificate),
|
||||||
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||||
|
@ -95,6 +95,23 @@ public class UnidentifiedAccessUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] getUnidentifiedAccessCertificate(@NonNull Recipient recipient) {
|
||||||
|
CertificateType certificateType;
|
||||||
|
PhoneNumberPrivacyValues.PhoneNumberSharingMode sendPhoneNumberTo = SignalStore.phoneNumberPrivacy().getPhoneNumberSharingMode();
|
||||||
|
|
||||||
|
switch (sendPhoneNumberTo) {
|
||||||
|
case EVERYONE: certificateType = CertificateType.UUID_AND_E164; break;
|
||||||
|
case CONTACTS: certificateType = recipient.isSystemContact() ? CertificateType.UUID_AND_E164 : CertificateType.UUID_ONLY; break;
|
||||||
|
case NOBODY : certificateType = CertificateType.UUID_ONLY; break;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, String.format("Certificate type for %s with setting %s -> %s", recipient.getId(), sendPhoneNumberTo, certificateType));
|
||||||
|
|
||||||
|
return SignalStore.certificateValues()
|
||||||
|
.getUnidentifiedAccessCertificate(certificateType);
|
||||||
|
}
|
||||||
|
|
||||||
private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) {
|
private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) {
|
||||||
ProfileKey theirProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey());
|
ProfileKey theirProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey());
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.CertificateType;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||||
|
@ -46,8 +48,8 @@ import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||||
|
@ -58,10 +60,12 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -330,23 +334,34 @@ public abstract class PushSendJob extends SendJob {
|
||||||
|
|
||||||
protected void rotateSenderCertificateIfNecessary() throws IOException {
|
protected void rotateSenderCertificateIfNecessary() throws IOException {
|
||||||
try {
|
try {
|
||||||
byte[] certificateBytes = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
|
Collection<CertificateType> requiredCertificateTypes = SignalStore.phoneNumberPrivacy()
|
||||||
|
.getRequiredCertificateTypes();
|
||||||
|
|
||||||
if (certificateBytes == null) {
|
Log.i(TAG, "Ensuring we have these certificates " + requiredCertificateTypes);
|
||||||
throw new InvalidCertificateException("No certificate was present.");
|
|
||||||
|
for (CertificateType certificateType : requiredCertificateTypes) {
|
||||||
|
|
||||||
|
byte[] certificateBytes = SignalStore.certificateValues()
|
||||||
|
.getUnidentifiedAccessCertificate(certificateType);
|
||||||
|
|
||||||
|
if (certificateBytes == null) {
|
||||||
|
throw new InvalidCertificateException(String.format("No certificate %s was present.", certificateType));
|
||||||
|
}
|
||||||
|
|
||||||
|
SenderCertificate certificate = new SenderCertificate(certificateBytes);
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > (certificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER)) {
|
||||||
|
throw new InvalidCertificateException(String.format(Locale.US, "Certificate %s is expired, or close to it. Expires on: %d, currently: %d", certificateType, certificate.getExpiration(), System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
Log.d(TAG, String.format("Certificate %s is valid", certificateType));
|
||||||
}
|
}
|
||||||
|
|
||||||
SenderCertificate certificate = new SenderCertificate(certificateBytes);
|
Log.d(TAG, "All certificates are valid.");
|
||||||
|
|
||||||
if (System.currentTimeMillis() > (certificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER)) {
|
|
||||||
throw new InvalidCertificateException("Certificate is expired, or close to it. Expires on: " + certificate.getExpiration() + ", currently: " + System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Certificate is valid.");
|
|
||||||
} catch (InvalidCertificateException e) {
|
} catch (InvalidCertificateException e) {
|
||||||
Log.w(TAG, "Certificate was invalid at send time. Fetching a new one.", e);
|
Log.w(TAG, "A certificate was invalid at send time. Fetching new ones.", e);
|
||||||
RotateCertificateJob certificateJob = new RotateCertificateJob(context);
|
if (!ApplicationDependencies.getJobManager().runSynchronously(new RotateCertificateJob(), 5000).isPresent()) {
|
||||||
certificateJob.onRun();
|
throw new IOException("Timeout rotating certificate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
package org.thoughtcrime.securesms.jobs;
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.CertificateType;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class RotateCertificateJob extends BaseJob {
|
public final class RotateCertificateJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "RotateCertificateJob";
|
public static final String KEY = "RotateCertificateJob";
|
||||||
|
|
||||||
private static final String TAG = RotateCertificateJob.class.getSimpleName();
|
private static final String TAG = Log.tag(RotateCertificateJob.class);
|
||||||
|
|
||||||
public RotateCertificateJob(Context context) {
|
public RotateCertificateJob() {
|
||||||
this(new Job.Parameters.Builder()
|
this(new Job.Parameters.Builder()
|
||||||
.setQueue("__ROTATE_SENDER_CERTIFICATE__")
|
.setQueue("__ROTATE_SENDER_CERTIFICATE__")
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
.setMaxAttempts(Parameters.UNLIMITED)
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
.build());
|
.build());
|
||||||
setContext(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RotateCertificateJob(@NonNull Job.Parameters parameters) {
|
private RotateCertificateJob(@NonNull Job.Parameters parameters) {
|
||||||
|
@ -57,10 +59,25 @@ public class RotateCertificateJob extends BaseJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (RotateCertificateJob.class) {
|
synchronized (RotateCertificateJob.class) {
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
byte[] certificate = accountManager.getSenderCertificate();
|
Collection<CertificateType> certificateTypes = SignalStore.phoneNumberPrivacy()
|
||||||
|
.getAllCertificateTypes();
|
||||||
|
|
||||||
TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate);
|
Log.i(TAG, "Rotating these certificates " + certificateTypes);
|
||||||
|
|
||||||
|
for (CertificateType certificateType: certificateTypes) {
|
||||||
|
byte[] certificate;
|
||||||
|
|
||||||
|
switch (certificateType) {
|
||||||
|
case UUID_AND_E164: certificate = accountManager.getSenderCertificate(); break;
|
||||||
|
case UUID_ONLY : certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy(); break;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, String.format("Successfully got %s certificate", certificateType));
|
||||||
|
SignalStore.certificateValues()
|
||||||
|
.setUnidentifiedAccessCertificate(certificateType, certificate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
public enum CertificateType {
|
||||||
|
UUID_AND_E164,
|
||||||
|
UUID_ONLY
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class CertificateValues extends SignalStoreValues {
|
||||||
|
|
||||||
|
private static final String UD_CERTIFICATE_UUID_AND_E164 = "certificate.uuidAndE164";
|
||||||
|
private static final String UD_CERTIFICATE_UUID_ONLY = "certificate.uuidOnly";
|
||||||
|
|
||||||
|
CertificateValues(@NonNull KeyValueStore store) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onFirstEverAppLaunch() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public void setUnidentifiedAccessCertificate(@NonNull CertificateType certificateType,
|
||||||
|
@Nullable byte[] certificate)
|
||||||
|
{
|
||||||
|
KeyValueStore.Writer writer = getStore().beginWrite();
|
||||||
|
|
||||||
|
switch (certificateType) {
|
||||||
|
case UUID_AND_E164: writer.putBlob(UD_CERTIFICATE_UUID_AND_E164, certificate); break;
|
||||||
|
case UUID_ONLY : writer.putBlob(UD_CERTIFICATE_UUID_ONLY, certificate); break;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable byte[] getUnidentifiedAccessCertificate(@NonNull CertificateType certificateType) {
|
||||||
|
switch (certificateType) {
|
||||||
|
case UUID_AND_E164: return getBlob(UD_CERTIFICATE_UUID_AND_E164, null);
|
||||||
|
case UUID_ONLY : return getBlob(UD_CERTIFICATE_UUID_ONLY, null);
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public final class PhoneNumberPrivacyValues extends SignalStoreValues {
|
||||||
|
|
||||||
|
public static final String SHARING_MODE = "phoneNumberPrivacy.sharingMode";
|
||||||
|
public static final String LISTING_MODE = "phoneNumberPrivacy.listingMode";
|
||||||
|
|
||||||
|
private static final Collection<CertificateType> REGULAR_CERTIFICATE = Collections.singletonList(CertificateType.UUID_AND_E164);
|
||||||
|
private static final Collection<CertificateType> PRIVACY_CERTIFICATE = Collections.singletonList(CertificateType.UUID_ONLY);
|
||||||
|
private static final Collection<CertificateType> BOTH_CERTIFICATES = Collections.unmodifiableCollection(Arrays.asList(CertificateType.UUID_AND_E164, CertificateType.UUID_ONLY));
|
||||||
|
|
||||||
|
PhoneNumberPrivacyValues(@NonNull KeyValueStore store) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onFirstEverAppLaunch() {
|
||||||
|
// TODO [ALAN] PhoneNumberPrivacy: During registration, set the attribute to so that new registrations start out as not listed
|
||||||
|
//getStore().beginWrite()
|
||||||
|
// .putInteger(LISTING_MODE, PhoneNumberListingMode.UNLISTED.ordinal())
|
||||||
|
// .apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull PhoneNumberSharingMode getPhoneNumberSharingMode() {
|
||||||
|
if (!FeatureFlags.phoneNumberPrivacy()) return PhoneNumberSharingMode.EVERYONE;
|
||||||
|
return PhoneNumberSharingMode.values()[getInteger(SHARING_MODE, PhoneNumberSharingMode.EVERYONE.ordinal())];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneNumberSharingMode(@NonNull PhoneNumberSharingMode phoneNumberSharingMode) {
|
||||||
|
putInteger(SHARING_MODE, phoneNumberSharingMode.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull PhoneNumberListingMode getPhoneNumberListingMode() {
|
||||||
|
if (!FeatureFlags.phoneNumberPrivacy()) return PhoneNumberListingMode.LISTED;
|
||||||
|
return PhoneNumberListingMode.values()[getInteger(LISTING_MODE, PhoneNumberListingMode.LISTED.ordinal())];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneNumberListingMode(@NonNull PhoneNumberListingMode phoneNumberListingMode) {
|
||||||
|
putInteger(LISTING_MODE, phoneNumberListingMode.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you respect {@link #getPhoneNumberSharingMode}, then you will only ever need to fetch and store
|
||||||
|
* these certificates types.
|
||||||
|
*/
|
||||||
|
public Collection<CertificateType> getRequiredCertificateTypes() {
|
||||||
|
switch (getPhoneNumberSharingMode()) {
|
||||||
|
case EVERYONE: return REGULAR_CERTIFICATE;
|
||||||
|
case CONTACTS: return BOTH_CERTIFICATES;
|
||||||
|
case NOBODY : return PRIVACY_CERTIFICATE;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All certificate types required according to the feature flags.
|
||||||
|
*/
|
||||||
|
public Collection<CertificateType> getAllCertificateTypes() {
|
||||||
|
return FeatureFlags.phoneNumberPrivacy() ? BOTH_CERTIFICATES : REGULAR_CERTIFICATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialized, do not change ordinal/order
|
||||||
|
*/
|
||||||
|
public enum PhoneNumberSharingMode {
|
||||||
|
EVERYONE,
|
||||||
|
CONTACTS,
|
||||||
|
NOBODY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialized, do not change ordinal/order
|
||||||
|
*/
|
||||||
|
public enum PhoneNumberListingMode {
|
||||||
|
LISTED,
|
||||||
|
UNLISTED
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,32 +13,36 @@ public final class SignalStore {
|
||||||
|
|
||||||
private static final SignalStore INSTANCE = new SignalStore();
|
private static final SignalStore INSTANCE = new SignalStore();
|
||||||
|
|
||||||
private final KeyValueStore store;
|
private final KeyValueStore store;
|
||||||
private final KbsValues kbsValues;
|
private final KbsValues kbsValues;
|
||||||
private final RegistrationValues registrationValues;
|
private final RegistrationValues registrationValues;
|
||||||
private final PinValues pinValues;
|
private final PinValues pinValues;
|
||||||
private final RemoteConfigValues remoteConfigValues;
|
private final RemoteConfigValues remoteConfigValues;
|
||||||
private final StorageServiceValues storageServiceValues;
|
private final StorageServiceValues storageServiceValues;
|
||||||
private final UiHints uiHints;
|
private final UiHints uiHints;
|
||||||
private final TooltipValues tooltipValues;
|
private final TooltipValues tooltipValues;
|
||||||
private final MiscellaneousValues misc;
|
private final MiscellaneousValues misc;
|
||||||
private final InternalValues internalValues;
|
private final InternalValues internalValues;
|
||||||
private final EmojiValues emojiValues;
|
private final EmojiValues emojiValues;
|
||||||
private final SettingsValues settingsValues;
|
private final SettingsValues settingsValues;
|
||||||
|
private final CertificateValues certificateValues;
|
||||||
|
private final PhoneNumberPrivacyValues phoneNumberPrivacyValues;
|
||||||
|
|
||||||
private SignalStore() {
|
private SignalStore() {
|
||||||
this.store = ApplicationDependencies.getKeyValueStore();
|
this.store = ApplicationDependencies.getKeyValueStore();
|
||||||
this.kbsValues = new KbsValues(store);
|
this.kbsValues = new KbsValues(store);
|
||||||
this.registrationValues = new RegistrationValues(store);
|
this.registrationValues = new RegistrationValues(store);
|
||||||
this.pinValues = new PinValues(store);
|
this.pinValues = new PinValues(store);
|
||||||
this.remoteConfigValues = new RemoteConfigValues(store);
|
this.remoteConfigValues = new RemoteConfigValues(store);
|
||||||
this.storageServiceValues = new StorageServiceValues(store);
|
this.storageServiceValues = new StorageServiceValues(store);
|
||||||
this.uiHints = new UiHints(store);
|
this.uiHints = new UiHints(store);
|
||||||
this.tooltipValues = new TooltipValues(store);
|
this.tooltipValues = new TooltipValues(store);
|
||||||
this.misc = new MiscellaneousValues(store);
|
this.misc = new MiscellaneousValues(store);
|
||||||
this.internalValues = new InternalValues(store);
|
this.internalValues = new InternalValues(store);
|
||||||
this.emojiValues = new EmojiValues(store);
|
this.emojiValues = new EmojiValues(store);
|
||||||
this.settingsValues = new SettingsValues(store);
|
this.settingsValues = new SettingsValues(store);
|
||||||
|
this.certificateValues = new CertificateValues(store);
|
||||||
|
this.phoneNumberPrivacyValues = new PhoneNumberPrivacyValues(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onFirstEverAppLaunch() {
|
public static void onFirstEverAppLaunch() {
|
||||||
|
@ -52,6 +56,8 @@ public final class SignalStore {
|
||||||
misc().onFirstEverAppLaunch();
|
misc().onFirstEverAppLaunch();
|
||||||
internalValues().onFirstEverAppLaunch();
|
internalValues().onFirstEverAppLaunch();
|
||||||
settings().onFirstEverAppLaunch();
|
settings().onFirstEverAppLaunch();
|
||||||
|
certificateValues().onFirstEverAppLaunch();
|
||||||
|
phoneNumberPrivacy().onFirstEverAppLaunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull KbsValues kbsValues() {
|
public static @NonNull KbsValues kbsValues() {
|
||||||
|
@ -98,6 +104,14 @@ public final class SignalStore {
|
||||||
return INSTANCE.settingsValues;
|
return INSTANCE.settingsValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull CertificateValues certificateValues() {
|
||||||
|
return INSTANCE.certificateValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull PhoneNumberPrivacyValues phoneNumberPrivacy() {
|
||||||
|
return INSTANCE.phoneNumberPrivacyValues;
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
||||||
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -58,7 +57,6 @@ public class UuidMigrationJob extends MigrationJob {
|
||||||
|
|
||||||
ensureSelfRecipientExists(context);
|
ensureSelfRecipientExists(context);
|
||||||
fetchOwnUuid(context);
|
fetchOwnUuid(context);
|
||||||
rotateSealedSenderCerts(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -78,14 +76,6 @@ public class UuidMigrationJob extends MigrationJob {
|
||||||
TextSecurePreferences.setLocalUuid(context, localUuid);
|
TextSecurePreferences.setLocalUuid(context, localUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void rotateSealedSenderCerts(@NonNull Context context) throws IOException {
|
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
|
||||||
byte[] certificate = accountManager.getSenderCertificate();
|
|
||||||
|
|
||||||
TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Factory implements Job.Factory<UuidMigrationJob> {
|
public static class Factory implements Job.Factory<UuidMigrationJob> {
|
||||||
@Override
|
@Override
|
||||||
public @NonNull UuidMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
public @NonNull UuidMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
|
|
@ -7,6 +7,9 @@ import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.TextAppearanceSpan;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -14,6 +17,7 @@ import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.autofill.HintConstants;
|
import androidx.autofill.HintConstants;
|
||||||
|
@ -37,6 +41,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||||
import org.thoughtcrime.securesms.keyvalue.PinValues;
|
import org.thoughtcrime.securesms.keyvalue.PinValues;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
|
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
@ -45,13 +50,13 @@ import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||||
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
|
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||||
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog;
|
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
@ -67,8 +72,10 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||||
|
|
||||||
private static final String TAG = Log.tag(AppProtectionPreferenceFragment.class);
|
private static final String TAG = Log.tag(AppProtectionPreferenceFragment.class);
|
||||||
|
|
||||||
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
|
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
|
||||||
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
|
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
|
||||||
|
private static final String PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER = "pref_who_can_see_phone_number";
|
||||||
|
private static final String PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER = "pref_who_can_find_by_phone_number";
|
||||||
|
|
||||||
private CheckBoxPreference disablePassphrase;
|
private CheckBoxPreference disablePassphrase;
|
||||||
|
|
||||||
|
@ -99,6 +106,18 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||||
this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
|
this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
|
||||||
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
||||||
|
|
||||||
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
|
Preference whoCanSeePhoneNumber = this.findPreference(PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER);
|
||||||
|
Preference whoCanFindByPhoneNumber = this.findPreference(PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER);
|
||||||
|
|
||||||
|
whoCanSeePhoneNumber.setPreferenceDataStore(null);
|
||||||
|
whoCanSeePhoneNumber.setOnPreferenceClickListener(new PhoneNumberPrivacyWhoCanSeeClickListener());
|
||||||
|
|
||||||
|
whoCanFindByPhoneNumber.setPreferenceDataStore(null);
|
||||||
|
whoCanFindByPhoneNumber.setOnPreferenceClickListener(new PhoneNumberPrivacyWhoCanFindClickListener());
|
||||||
|
} else {
|
||||||
|
this.findPreference("category_phone_number_privacy").setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
SwitchPreferenceCompat linkPreviewPref = (SwitchPreferenceCompat) this.findPreference(SettingsValues.LINK_PREVIEWS);
|
SwitchPreferenceCompat linkPreviewPref = (SwitchPreferenceCompat) this.findPreference(SettingsValues.LINK_PREVIEWS);
|
||||||
linkPreviewPref.setChecked(SignalStore.settings().isLinkPreviewsEnabled());
|
linkPreviewPref.setChecked(SignalStore.settings().isLinkPreviewsEnabled());
|
||||||
|
@ -138,6 +157,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||||
signalPinReminders.setEnabled(false);
|
signalPinReminders.setEnabled(false);
|
||||||
registrationLockV2.setEnabled(false);
|
registrationLockV2.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializePhoneNumberPrivacyWhoCanSeeSummary();
|
||||||
|
initializePhoneNumberPrivacyWhoCanFindSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -164,6 +186,27 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||||
String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds));
|
String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializePhoneNumberPrivacyWhoCanSeeSummary() {
|
||||||
|
Preference preference = findPreference(PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER);
|
||||||
|
|
||||||
|
switch (SignalStore.phoneNumberPrivacy().getPhoneNumberSharingMode()) {
|
||||||
|
case EVERYONE: preference.setSummary(R.string.PhoneNumberPrivacy_everyone); break;
|
||||||
|
case CONTACTS: preference.setSummary(R.string.PhoneNumberPrivacy_my_contacts); break;
|
||||||
|
case NOBODY : preference.setSummary(R.string.PhoneNumberPrivacy_nobody); break;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePhoneNumberPrivacyWhoCanFindSummary() {
|
||||||
|
Preference preference = findPreference(PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER);
|
||||||
|
|
||||||
|
switch (SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode()) {
|
||||||
|
case LISTED : preference.setSummary(R.string.PhoneNumberPrivacy_everyone); break;
|
||||||
|
case UNLISTED: preference.setSummary(R.string.PhoneNumberPrivacy_nobody); break;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeVisibility() {
|
private void initializeVisibility() {
|
||||||
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
||||||
findPreference("pref_enable_passphrase_temporary").setVisible(false);
|
findPreference("pref_enable_passphrase_temporary").setVisible(false);
|
||||||
|
@ -504,4 +547,86 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class PhoneNumberPrivacyWhoCanSeeClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
PhoneNumberPrivacyValues phoneNumberPrivacyValues = SignalStore.phoneNumberPrivacy();
|
||||||
|
|
||||||
|
final PhoneNumberPrivacyValues.PhoneNumberSharingMode[] value = { phoneNumberPrivacyValues.getPhoneNumberSharingMode() };
|
||||||
|
|
||||||
|
new AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.preferences_app_protection__see_my_phone_number)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setSingleChoiceItems(items(requireContext()), value[0].ordinal(), (dialog, which) -> value[0] = PhoneNumberPrivacyValues.PhoneNumberSharingMode.values()[which])
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
|
PhoneNumberPrivacyValues.PhoneNumberSharingMode phoneNumberSharingMode = value[0];
|
||||||
|
phoneNumberPrivacyValues.setPhoneNumberSharingMode(phoneNumberSharingMode);
|
||||||
|
Log.i(TAG, String.format("PhoneNumberSharingMode changed to %s. Scheduling storage value sync", phoneNumberSharingMode));
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange();
|
||||||
|
initializePhoneNumberPrivacyWhoCanSeeSummary();
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence[] items(Context context) {
|
||||||
|
return new CharSequence[]{
|
||||||
|
titleAndDescription(context, context.getString(R.string.PhoneNumberPrivacy_everyone), context.getString(R.string.PhoneNumberPrivacy_everyone_see_description)),
|
||||||
|
titleAndDescription(context, context.getString(R.string.PhoneNumberPrivacy_my_contacts), context.getString(R.string.PhoneNumberPrivacy_my_contacts_see_description)),
|
||||||
|
context.getString(R.string.PhoneNumberPrivacy_nobody) };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PhoneNumberPrivacyWhoCanFindClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
PhoneNumberPrivacyValues phoneNumberPrivacyValues = SignalStore.phoneNumberPrivacy();
|
||||||
|
|
||||||
|
final PhoneNumberPrivacyValues.PhoneNumberListingMode[] value = { phoneNumberPrivacyValues.getPhoneNumberListingMode() };
|
||||||
|
|
||||||
|
new AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.preferences_app_protection__find_me_by_phone_number)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setSingleChoiceItems(items(requireContext()),
|
||||||
|
value[0].ordinal(),
|
||||||
|
(dialog, which) -> value[0] = PhoneNumberPrivacyValues.PhoneNumberListingMode.values()[which])
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
|
PhoneNumberPrivacyValues.PhoneNumberListingMode phoneNumberListingMode = value[0];
|
||||||
|
phoneNumberPrivacyValues.setPhoneNumberListingMode(phoneNumberListingMode);
|
||||||
|
Log.i(TAG, String.format("PhoneNumberListingMode changed to %s. Scheduling storage value sync", phoneNumberListingMode));
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange();
|
||||||
|
initializePhoneNumberPrivacyWhoCanFindSummary();
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence[] items(Context context) {
|
||||||
|
return new CharSequence[]{
|
||||||
|
titleAndDescription(context, context.getString(R.string.PhoneNumberPrivacy_everyone), context.getString(R.string.PhoneNumberPrivacy_everyone_find_description)),
|
||||||
|
context.getString(R.string.PhoneNumberPrivacy_nobody) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a detail row for radio group descriptions. */
|
||||||
|
private static CharSequence titleAndDescription(@NonNull Context context, @NonNull String header, @NonNull String description) {
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
|
||||||
|
builder.append("\n");
|
||||||
|
builder.append(header);
|
||||||
|
builder.append("\n");
|
||||||
|
|
||||||
|
builder.setSpan(new TextAppearanceSpan(context, android.R.style.TextAppearance_Small), builder.length(), builder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
|
||||||
|
builder.append(description);
|
||||||
|
builder.append("\n");
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ public final class CodeVerificationRequest {
|
||||||
private static void handleSuccessfulRegistration(@NonNull Context context) {
|
private static void handleSuccessfulRegistration(@NonNull Context context) {
|
||||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||||
jobManager.add(new DirectoryRefreshJob(false));
|
jobManager.add(new DirectoryRefreshJob(false));
|
||||||
jobManager.add(new RotateCertificateJob(context));
|
jobManager.add(new RotateCertificateJob());
|
||||||
|
|
||||||
DirectoryRefreshListener.schedule(context);
|
DirectoryRefreshListener.schedule(context);
|
||||||
RotateSignedPreKeyListener.schedule(context);
|
RotateSignedPreKeyListener.schedule(context);
|
||||||
|
|
|
@ -4,7 +4,6 @@ package org.thoughtcrime.securesms.service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
@ -22,7 +21,7 @@ public class RotateSenderCertificateListener extends PersistentAlarmManagerListe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long onAlarm(Context context, long scheduledTime) {
|
protected long onAlarm(Context context, long scheduledTime) {
|
||||||
ApplicationDependencies.getJobManager().add(new RotateCertificateJob(context));
|
ApplicationDependencies.getJobManager().add(new RotateCertificateJob());
|
||||||
|
|
||||||
long nextTime = System.currentTimeMillis() + INTERVAL;
|
long nextTime = System.currentTimeMillis() + INTERVAL;
|
||||||
TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime);
|
TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
|
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
|
||||||
|
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -55,16 +56,18 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
|
||||||
familyName = local.getFamilyName().or("");
|
familyName = local.getFamilyName().or("");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] unknownFields = remote.serializeUnknownFields();
|
byte[] unknownFields = remote.serializeUnknownFields();
|
||||||
String avatarUrlPath = remote.getAvatarUrlPath().or(local.getAvatarUrlPath()).or("");
|
String avatarUrlPath = remote.getAvatarUrlPath().or(local.getAvatarUrlPath()).or("");
|
||||||
byte[] profileKey = remote.getProfileKey().or(local.getProfileKey()).orNull();
|
byte[] profileKey = remote.getProfileKey().or(local.getProfileKey()).orNull();
|
||||||
boolean noteToSelfArchived = remote.isNoteToSelfArchived();
|
boolean noteToSelfArchived = remote.isNoteToSelfArchived();
|
||||||
boolean readReceipts = remote.isReadReceiptsEnabled();
|
boolean readReceipts = remote.isReadReceiptsEnabled();
|
||||||
boolean typingIndicators = remote.isTypingIndicatorsEnabled();
|
boolean typingIndicators = remote.isTypingIndicatorsEnabled();
|
||||||
boolean sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled();
|
boolean sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled();
|
||||||
boolean linkPreviews = remote.isLinkPreviewsEnabled();
|
boolean linkPreviews = remote.isLinkPreviewsEnabled();
|
||||||
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews);
|
boolean unlisted = remote.isPhoneNumberUnlisted();
|
||||||
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews);
|
AccountRecord.PhoneNumberSharingMode phoneNumberSharingMode = remote.getPhoneNumberSharingMode();
|
||||||
|
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted);
|
||||||
|
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted );
|
||||||
|
|
||||||
if (matchesRemote) {
|
if (matchesRemote) {
|
||||||
return remote;
|
return remote;
|
||||||
|
@ -82,6 +85,9 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
|
||||||
.setTypingIndicatorsEnabled(typingIndicators)
|
.setTypingIndicatorsEnabled(typingIndicators)
|
||||||
.setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
|
.setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
|
||||||
.setLinkPreviewsEnabled(linkPreviews)
|
.setLinkPreviewsEnabled(linkPreviews)
|
||||||
|
.setUnlistedPhoneNumber(unlisted)
|
||||||
|
.setPhoneNumberSharingMode(phoneNumberSharingMode)
|
||||||
|
.setUnlistedPhoneNumber(unlisted)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +102,9 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
|
||||||
boolean readReceipts,
|
boolean readReceipts,
|
||||||
boolean typingIndicators,
|
boolean typingIndicators,
|
||||||
boolean sealedSenderIndicators,
|
boolean sealedSenderIndicators,
|
||||||
boolean linkPreviewsEnabled)
|
boolean linkPreviewsEnabled,
|
||||||
|
AccountRecord.PhoneNumberSharingMode phoneNumberSharingMode,
|
||||||
|
boolean unlistedPhoneNumber)
|
||||||
{
|
{
|
||||||
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
||||||
Objects.equals(contact.getGivenName().or(""), givenName) &&
|
Objects.equals(contact.getGivenName().or(""), givenName) &&
|
||||||
|
@ -107,6 +115,8 @@ class AccountConflictMerger implements StorageSyncHelper.ConflictMerger<SignalAc
|
||||||
contact.isReadReceiptsEnabled() == readReceipts &&
|
contact.isReadReceiptsEnabled() == readReceipts &&
|
||||||
contact.isTypingIndicatorsEnabled() == typingIndicators &&
|
contact.isTypingIndicatorsEnabled() == typingIndicators &&
|
||||||
contact.isSealedSenderIndicatorsEnabled() == sealedSenderIndicators &&
|
contact.isSealedSenderIndicatorsEnabled() == sealedSenderIndicators &&
|
||||||
contact.isLinkPreviewsEnabled() == linkPreviewsEnabled;
|
contact.isLinkPreviewsEnabled() == linkPreviewsEnabled &&
|
||||||
|
contact.getPhoneNumberSharingMode() == phoneNumberSharingMode &&
|
||||||
|
contact.isPhoneNumberUnlisted() == unlistedPhoneNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,11 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
import org.thoughtcrime.securesms.util.SetUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
||||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -398,11 +399,22 @@ public final class StorageSyncHelper {
|
||||||
.setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context))
|
.setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context))
|
||||||
.setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context))
|
.setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context))
|
||||||
.setLinkPreviewsEnabled(SignalStore.settings().isLinkPreviewsEnabled())
|
.setLinkPreviewsEnabled(SignalStore.settings().isLinkPreviewsEnabled())
|
||||||
|
.setUnlistedPhoneNumber(SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode() == PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED)
|
||||||
|
.setPhoneNumberSharingMode(localToRemotePhoneNumberSharingMode(SignalStore.phoneNumberPrivacy().getPhoneNumberSharingMode()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return SignalStorageRecord.forAccount(account);
|
return SignalStorageRecord.forAccount(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AccountRecord.PhoneNumberSharingMode localToRemotePhoneNumberSharingMode(PhoneNumberPrivacyValues.PhoneNumberSharingMode phoneNumberPhoneNumberSharingMode) {
|
||||||
|
switch (phoneNumberPhoneNumberSharingMode) {
|
||||||
|
case EVERYONE: return AccountRecord.PhoneNumberSharingMode.EVERYBODY;
|
||||||
|
case CONTACTS: return AccountRecord.PhoneNumberSharingMode.CONTACTS_ONLY;
|
||||||
|
case NOBODY : return AccountRecord.PhoneNumberSharingMode.NOBODY;
|
||||||
|
default : throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void applyAccountStorageSyncUpdates(@NonNull Context context, Optional<StorageSyncHelper.RecordUpdate<SignalAccountRecord>> update) {
|
public static void applyAccountStorageSyncUpdates(@NonNull Context context, Optional<StorageSyncHelper.RecordUpdate<SignalAccountRecord>> update) {
|
||||||
if (!update.isPresent()) {
|
if (!update.isPresent()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -49,20 +49,21 @@ public final class FeatureFlags {
|
||||||
|
|
||||||
private static final long FETCH_INTERVAL = TimeUnit.HOURS.toMillis(2);
|
private static final long FETCH_INTERVAL = TimeUnit.HOURS.toMillis(2);
|
||||||
|
|
||||||
private static final String USERNAMES = "android.usernames";
|
private static final String USERNAMES = "android.usernames";
|
||||||
private static final String ATTACHMENTS_V3 = "android.attachmentsV3.2";
|
private static final String ATTACHMENTS_V3 = "android.attachmentsV3.2";
|
||||||
private static final String REMOTE_DELETE = "android.remoteDelete";
|
private static final String REMOTE_DELETE = "android.remoteDelete";
|
||||||
private static final String GROUPS_V2_OLD_1 = "android.groupsv2";
|
private static final String GROUPS_V2_OLD_1 = "android.groupsv2";
|
||||||
private static final String GROUPS_V2_OLD_2 = "android.groupsv2.2";
|
private static final String GROUPS_V2_OLD_2 = "android.groupsv2.2";
|
||||||
private static final String GROUPS_V2 = "android.groupsv2.3";
|
private static final String GROUPS_V2 = "android.groupsv2.3";
|
||||||
private static final String GROUPS_V2_CREATE = "android.groupsv2.create.3";
|
private static final String GROUPS_V2_CREATE = "android.groupsv2.create.3";
|
||||||
private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion";
|
private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion";
|
||||||
private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion";
|
private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion";
|
||||||
private static final String GROUPS_V2_CAPACITY = "global.groupsv2.maxGroupSize";
|
private static final String GROUPS_V2_CAPACITY = "global.groupsv2.maxGroupSize";
|
||||||
private static final String CDS_VERSION = "android.cdsVersion";
|
private static final String CDS_VERSION = "android.cdsVersion";
|
||||||
private static final String INTERNAL_USER = "android.internalUser";
|
private static final String INTERNAL_USER = "android.internalUser";
|
||||||
private static final String MENTIONS = "android.mentions";
|
private static final String MENTIONS = "android.mentions";
|
||||||
private static final String VERIFY_V2 = "android.verifyV2";
|
private static final String VERIFY_V2 = "android.verifyV2";
|
||||||
|
private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||||
|
@ -279,6 +280,14 @@ public final class FeatureFlags {
|
||||||
return getBoolean(VERIFY_V2, false);
|
return getBoolean(VERIFY_V2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user can choose phone number privacy settings, and;
|
||||||
|
* Whether to fetch and store the secondary certificate
|
||||||
|
*/
|
||||||
|
public static boolean phoneNumberPrivacy() {
|
||||||
|
return getVersionFlag(PHONE_NUMBER_PRIVACY_VERSION) == VersionFlag.ON;
|
||||||
|
}
|
||||||
|
|
||||||
/** Only for rendering debug info. */
|
/** Only for rendering debug info. */
|
||||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||||
return new TreeMap<>(REMOTE_VALUES);
|
return new TreeMap<>(REMOTE_VALUES);
|
||||||
|
|
|
@ -182,7 +182,6 @@ public class TextSecurePreferences {
|
||||||
private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull";
|
private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull";
|
||||||
|
|
||||||
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time";
|
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time";
|
||||||
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate_uuid";
|
|
||||||
public static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
|
public static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
|
||||||
public static final String SHOW_UNIDENTIFIED_DELIVERY_INDICATORS = "pref_show_unidentifed_delivery_indicators";
|
public static final String SHOW_UNIDENTIFIED_DELIVERY_INDICATORS = "pref_show_unidentifed_delivery_indicators";
|
||||||
private static final String UNIDENTIFIED_DELIVERY_ENABLED = "pref_unidentified_delivery_enabled";
|
private static final String UNIDENTIFIED_DELIVERY_ENABLED = "pref_unidentified_delivery_enabled";
|
||||||
|
@ -598,26 +597,6 @@ public class TextSecurePreferences {
|
||||||
setLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, value);
|
setLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setUnidentifiedAccessCertificate(Context context, byte[] value) {
|
|
||||||
setStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, Base64.encodeBytes(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getUnidentifiedAccessCertificate(Context context) {
|
|
||||||
return parseCertificate(getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] parseCertificate(String raw) {
|
|
||||||
try {
|
|
||||||
if (raw != null) {
|
|
||||||
return Base64.decode(raw);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isUniversalUnidentifiedAccess(Context context) {
|
public static boolean isUniversalUnidentifiedAccess(Context context) {
|
||||||
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
|
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,8 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/name_container" />
|
app:layout_constraintTop_toBottomOf="@id/name_container"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/profile_overview_username"
|
android:id="@+id/profile_overview_username"
|
||||||
|
@ -182,7 +183,8 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toStartOf="@id/profile_overview_username_edit_button"
|
app:layout_constraintEnd_toStartOf="@id/profile_overview_username_edit_button"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/profile_overview_username_label" />
|
app:layout_constraintTop_toBottomOf="@id/profile_overview_username_label"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/profile_overview_username_edit_button"
|
android:id="@+id/profile_overview_username_edit_button"
|
||||||
|
@ -196,7 +198,8 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/profile_overview_username"
|
app:layout_constraintBottom_toBottomOf="@id/profile_overview_username"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/profile_overview_username"
|
app:layout_constraintTop_toTopOf="@id/profile_overview_username"
|
||||||
app:srcCompat="@drawable/ic_compose_solid_24" />
|
app:srcCompat="@drawable/ic_compose_solid_24"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||||
android:id="@+id/description_text"
|
android:id="@+id/description_text"
|
||||||
|
|
|
@ -2106,6 +2106,7 @@
|
||||||
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Disable Signal\'s built-in emoji support</string>
|
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Disable Signal\'s built-in emoji support</string>
|
||||||
<string name="preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address">Relay all calls through the Signal server to avoid revealing your IP address to your contact. Enabling will reduce call quality.</string>
|
<string name="preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address">Relay all calls through the Signal server to avoid revealing your IP address to your contact. Enabling will reduce call quality.</string>
|
||||||
<string name="preferences_advanced__always_relay_calls">Always relay calls</string>
|
<string name="preferences_advanced__always_relay_calls">Always relay calls</string>
|
||||||
|
<string name="preferences_app_protection__who_can">Who can…</string>
|
||||||
<string name="preferences_app_protection__app_access">App access</string>
|
<string name="preferences_app_protection__app_access">App access</string>
|
||||||
<string name="preferences_app_protection__communication">Communication</string>
|
<string name="preferences_app_protection__communication">Communication</string>
|
||||||
<string name="preferences_chats__chats">Chats</string>
|
<string name="preferences_chats__chats">Chats</string>
|
||||||
|
@ -2505,6 +2506,14 @@
|
||||||
<string name="RegistrationActivity_code_support_subject">Signal Registration - Verification Code for Android</string>
|
<string name="RegistrationActivity_code_support_subject">Signal Registration - Verification Code for Android</string>
|
||||||
<string name="BackupUtil_never">Never</string>
|
<string name="BackupUtil_never">Never</string>
|
||||||
<string name="BackupUtil_unknown">Unknown</string>
|
<string name="BackupUtil_unknown">Unknown</string>
|
||||||
|
<string name="preferences_app_protection__see_my_phone_number">See my phone number</string>
|
||||||
|
<string name="preferences_app_protection__find_me_by_phone_number">Find me by phone number</string>
|
||||||
|
<string name="PhoneNumberPrivacy_everyone">Everyone</string>
|
||||||
|
<string name="PhoneNumberPrivacy_my_contacts">My contacts</string>
|
||||||
|
<string name="PhoneNumberPrivacy_nobody">Nobody</string>
|
||||||
|
<string name="PhoneNumberPrivacy_everyone_see_description">Your phone number will be visible to all people and groups you message.</string>
|
||||||
|
<string name="PhoneNumberPrivacy_everyone_find_description">Anyone who has your phone number in their contacts will see you as a contact on Signal. Others will be able to find you in search.</string>
|
||||||
|
<string name="PhoneNumberPrivacy_my_contacts_see_description">Only your contacts will see your phone number on Signal.</string>
|
||||||
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
||||||
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Lock Signal access with Android screen lock or fingerprint</string>
|
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Lock Signal access with Android screen lock or fingerprint</string>
|
||||||
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Screen lock inactivity timeout</string>
|
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Screen lock inactivity timeout</string>
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="category_phone_number_privacy"
|
||||||
|
android:title="@string/preferences_app_protection__who_can">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="pref_who_can_see_phone_number"
|
||||||
|
android:title="@string/preferences_app_protection__see_my_phone_number" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="pref_who_can_find_by_phone_number"
|
||||||
|
android:title="@string/preferences_app_protection__find_me_by_phone_number" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/preferences_app_protection__app_access">
|
<PreferenceCategory android:title="@string/preferences_app_protection__app_access">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||||
|
|
|
@ -9,7 +9,6 @@ package org.whispersystems.signalservice.api;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
@ -80,13 +79,10 @@ import java.security.KeyStore;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.sql.Time;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -148,8 +144,8 @@ public class SignalServiceAccountManager {
|
||||||
return this.pushServiceSocket.getSenderCertificate();
|
return this.pushServiceSocket.getSenderCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSenderCertificateLegacy() throws IOException {
|
public byte[] getSenderCertificateForPhoneNumberPrivacy() throws IOException {
|
||||||
return this.pushServiceSocket.getSenderCertificateLegacy();
|
return this.pushServiceSocket.getUuidOnlySenderCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -586,6 +586,7 @@ public class SignalServiceMessageSender {
|
||||||
quoteBuilder.setAuthorUuid(message.getQuote().get().getAuthor().getUuid().get().toString());
|
quoteBuilder.setAuthorUuid(message.getQuote().get().getAuthor().getUuid().get().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO [Alan] PhoneNumberPrivacy: Do not set this number
|
||||||
if (message.getQuote().get().getAuthor().getNumber().isPresent()) {
|
if (message.getQuote().get().getAuthor().getNumber().isPresent()) {
|
||||||
quoteBuilder.setAuthorE164(message.getQuote().get().getAuthor().getNumber().get());
|
quoteBuilder.setAuthorE164(message.getQuote().get().getAuthor().getNumber().get());
|
||||||
}
|
}
|
||||||
|
@ -682,6 +683,7 @@ public class SignalServiceMessageSender {
|
||||||
.setRemove(message.getReaction().get().isRemove())
|
.setRemove(message.getReaction().get().isRemove())
|
||||||
.setTargetSentTimestamp(message.getReaction().get().getTargetSentTimestamp());
|
.setTargetSentTimestamp(message.getReaction().get().getTargetSentTimestamp());
|
||||||
|
|
||||||
|
// TODO [Alan] PhoneNumberPrivacy: Do not set this number
|
||||||
if (message.getReaction().get().getTargetAuthor().getNumber().isPresent()) {
|
if (message.getReaction().get().getTargetAuthor().getNumber().isPresent()) {
|
||||||
reactionBuilder.setTargetAuthorE164(message.getReaction().get().getTargetAuthor().getNumber().get());
|
reactionBuilder.setTargetAuthorE164(message.getReaction().get().getTargetAuthor().getNumber().get());
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,14 @@ public final class SignalAccountRecord implements SignalRecord {
|
||||||
return proto.getLinkPreviews();
|
return proto.getLinkPreviews();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountRecord.PhoneNumberSharingMode getPhoneNumberSharingMode() {
|
||||||
|
return proto.getPhoneNumberSharingMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPhoneNumberUnlisted() {
|
||||||
|
return proto.getUnlistedPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
AccountRecord toProto() {
|
AccountRecord toProto() {
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
|
@ -159,6 +167,16 @@ public final class SignalAccountRecord implements SignalRecord {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setPhoneNumberSharingMode(AccountRecord.PhoneNumberSharingMode mode) {
|
||||||
|
builder.setPhoneNumberSharingMode(mode);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setUnlistedPhoneNumber(boolean unlisted) {
|
||||||
|
builder.setUnlistedPhoneNumber(unlisted);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SignalAccountRecord build() {
|
public SignalAccountRecord build() {
|
||||||
AccountRecord proto = builder.build();
|
AccountRecord proto = builder.build();
|
||||||
|
|
||||||
|
|
|
@ -181,8 +181,8 @@ public class PushServiceSocket {
|
||||||
private static final String PROFILE_PATH = "/v1/profile/%s";
|
private static final String PROFILE_PATH = "/v1/profile/%s";
|
||||||
private static final String PROFILE_USERNAME_PATH = "/v1/profile/username/%s";
|
private static final String PROFILE_USERNAME_PATH = "/v1/profile/username/%s";
|
||||||
|
|
||||||
private static final String SENDER_CERTIFICATE_LEGACY_PATH = "/v1/certificate/delivery";
|
private static final String SENDER_CERTIFICATE_PATH = "/v1/certificate/delivery?includeUuid=true";
|
||||||
private static final String SENDER_CERTIFICATE_PATH = "/v1/certificate/delivery?includeUuid=true";
|
private static final String SENDER_CERTIFICATE_NO_E164_PATH = "/v1/certificate/delivery?includeUuid=true&includeE164=false";
|
||||||
|
|
||||||
private static final String KBS_AUTH_PATH = "/v1/backup/auth";
|
private static final String KBS_AUTH_PATH = "/v1/backup/auth";
|
||||||
|
|
||||||
|
@ -363,13 +363,13 @@ public class PushServiceSocket {
|
||||||
makeServiceRequest(REGISTRATION_LOCK_PATH, "DELETE", null);
|
makeServiceRequest(REGISTRATION_LOCK_PATH, "DELETE", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSenderCertificateLegacy() throws IOException {
|
public byte[] getSenderCertificate() throws IOException {
|
||||||
String responseText = makeServiceRequest(SENDER_CERTIFICATE_LEGACY_PATH, "GET", null);
|
String responseText = makeServiceRequest(SENDER_CERTIFICATE_PATH, "GET", null);
|
||||||
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSenderCertificate() throws IOException {
|
public byte[] getUuidOnlySenderCertificate() throws IOException {
|
||||||
String responseText = makeServiceRequest(SENDER_CERTIFICATE_PATH, "GET", null);
|
String responseText = makeServiceRequest(SENDER_CERTIFICATE_NO_E164_PATH, "GET", null);
|
||||||
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,15 +97,24 @@ message GroupV2Record {
|
||||||
}
|
}
|
||||||
|
|
||||||
message AccountRecord {
|
message AccountRecord {
|
||||||
bytes profileKey = 1;
|
|
||||||
string givenName = 2;
|
enum PhoneNumberSharingMode {
|
||||||
string familyName = 3;
|
EVERYBODY = 0;
|
||||||
string avatarUrlPath = 4;
|
CONTACTS_ONLY = 1;
|
||||||
bool noteToSelfArchived = 5;
|
NOBODY = 2;
|
||||||
bool readReceipts = 6;
|
}
|
||||||
bool sealedSenderIndicators = 7;
|
|
||||||
bool typingIndicators = 8;
|
bytes profileKey = 1;
|
||||||
bool proxiedLinkPreviews = 9;
|
string givenName = 2;
|
||||||
|
string familyName = 3;
|
||||||
|
string avatarUrlPath = 4;
|
||||||
|
bool noteToSelfArchived = 5;
|
||||||
|
bool readReceipts = 6;
|
||||||
|
bool sealedSenderIndicators = 7;
|
||||||
|
bool typingIndicators = 8;
|
||||||
|
bool proxiedLinkPreviews = 9;
|
||||||
// 10 is reserved for unread
|
// 10 is reserved for unread
|
||||||
bool linkPreviews = 11;
|
bool linkPreviews = 11;
|
||||||
|
PhoneNumberSharingMode phoneNumberSharingMode = 12;
|
||||||
|
bool unlistedPhoneNumber = 13;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue