Support additional sync behavior for linked devices.

master
Greyson Parrelli 2019-11-08 15:33:10 -05:00 committed by Alan Evans
parent 995569dd50
commit c702ff676a
16 changed files with 401 additions and 59 deletions

View File

@ -115,7 +115,7 @@ dependencies {
implementation 'org.conscrypt:conscrypt-android:2.0.0'
implementation 'org.signal:aesgcmprovider:0.0.3'
implementation 'org.whispersystems:signal-service-android:2.15.1'
implementation 'org.whispersystems:signal-service-android:2.15.3'
implementation 'org.signal:ringrtc-android:0.1.9'

View File

@ -39,6 +39,8 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
@ -380,6 +382,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
}
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
return true;
}

View File

@ -48,8 +48,9 @@ public class UnidentifiedAccessUtil {
try {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context) ;
byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() && Recipient.self().isUuidSupported()
? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);

View File

@ -16,12 +16,15 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.Closeable;
@ -762,6 +765,50 @@ public class RecipientDatabase extends Database {
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
public void applyBlockedUpdate(@NonNull List<SignalServiceAddress> blocked, List<byte[]> groupIds) {
List<String> blockedE164 = Stream.of(blocked)
.filter(b -> b.getNumber().isPresent())
.map(b -> b.getNumber().get())
.toList();
List<String> blockedUuid = Stream.of(blocked)
.filter(b -> b.getUuid().isPresent())
.map(b -> b.getUuid().get().toString().toLowerCase())
.toList();
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
try {
ContentValues resetBlocked = new ContentValues();
resetBlocked.put(BLOCKED, 0);
db.update(TABLE_NAME, resetBlocked, null, null);
ContentValues setBlocked = new ContentValues();
setBlocked.put(BLOCKED, 1);
for (String e164 : blockedE164) {
db.update(TABLE_NAME, setBlocked, PHONE + " = ?", new String[] { e164 });
}
for (String uuid : blockedUuid) {
db.update(TABLE_NAME, setBlocked, UUID + " = ?", new String[] { uuid });
}
List<String> groupIdStrings = Stream.of(groupIds).map(g -> GroupUtil.getEncodedId(g, false)).toList();
for (String groupId : groupIdStrings) {
db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId });
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ApplicationDependencies.getRecipientCache().clear();
}
private int update(@NonNull RecipientId id, ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
return database.update(TABLE_NAME, contentValues, ID + " = ?", new String[] { id.serialize() });

View File

@ -51,6 +51,7 @@ public final class JobManagerFactories {
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory());
put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory());
put(MultiDeviceProfileContentUpdateJob.KEY, new MultiDeviceProfileContentUpdateJob.Factory());
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
@ -64,6 +65,7 @@ public final class JobManagerFactories {
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());

View File

@ -0,0 +1,74 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
public class MultiDeviceProfileContentUpdateJob extends BaseJob {
public static final String KEY = "MultiDeviceProfileContentUpdateJob";
private static final String TAG = Log.tag(MultiDeviceProfileContentUpdateJob.class);
public MultiDeviceProfileContentUpdateJob() {
this(new Parameters.Builder()
.setQueue("MultiDeviceProfileUpdateJob")
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
.build());
}
private MultiDeviceProfileContentUpdateJob(@NonNull Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
protected void onRun() throws Exception {
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "Not multi device, aborting...");
return;
}
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
messageSender.sendMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE),
UnidentifiedAccessUtil.getAccessForSync(context));
}
@Override
protected boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "Did not succeed!");
}
public static final class Factory implements Job.Factory<MultiDeviceProfileContentUpdateJob> {
@Override
public @NonNull MultiDeviceProfileContentUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new MultiDeviceProfileContentUpdateJob(parameters);
}
}
}

View File

@ -114,6 +114,8 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
@ -268,6 +270,9 @@ public class PushDecryptJob extends BaseJob {
else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(syncMessage.getViewOnceOpen().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
else if (syncMessage.getConfiguration().isPresent()) handleSynchronizeConfigurationMessage(syncMessage.getConfiguration().get());
else if (syncMessage.getBlockedList().isPresent()) handleSynchronizeBlockedListMessage(syncMessage.getBlockedList().get());
else if (syncMessage.getFetchType().isPresent()) handleSynchronizeFetchMessage(syncMessage.getFetchType().get());
else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message...");
@ -546,6 +551,36 @@ public class PushDecryptJob extends BaseJob {
}
}
private void handleSynchronizeConfigurationMessage(@NonNull ConfigurationMessage configurationMessage) {
if (configurationMessage.getReadReceipts().isPresent()) {
TextSecurePreferences.setReadReceiptsEnabled(context, configurationMessage.getReadReceipts().get());
}
if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) {
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, configurationMessage.getReadReceipts().get());
}
if (configurationMessage.getTypingIndicators().isPresent()) {
TextSecurePreferences.setTypingIndicatorsEnabled(context, configurationMessage.getTypingIndicators().get());
}
if (configurationMessage.getLinkPreviews().isPresent()) {
TextSecurePreferences.setLinkPreviewsEnabled(context, configurationMessage.getReadReceipts().get());
}
}
private void handleSynchronizeBlockedListMessage(@NonNull BlockedListMessage blockMessage) {
DatabaseFactory.getRecipientDatabase(context).applyBlockedUpdate(blockMessage.getAddresses(), blockMessage.getGroupIds());
}
private void handleSynchronizeFetchMessage(@NonNull SignalServiceSyncMessage.FetchType fetchType) {
if (fetchType == SignalServiceSyncMessage.FetchType.LOCAL_PROFILE) {
ApplicationDependencies.getJobManager().add(new RefreshOwnProfileJob());
} else {
Log.w(TAG, "Received a fetch message for an unknown type.");
}
}
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
@NonNull SentTranscriptMessage message)
throws StorageFailedException

View File

@ -0,0 +1,106 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
/**
* Refreshes the profile of the local user. Different from {@link RetrieveProfileJob} in that we
* have to sometimes look at/set different data stores, and we will *always* do the fetch regardless
* of caching.
*/
public class RefreshOwnProfileJob extends BaseJob {
public static final String KEY = "RefreshOwnProfileJob";
private static final String TAG = Log.tag(RefreshOwnProfileJob.class);
public RefreshOwnProfileJob() {
this(new Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue("RefreshOwnProfileJob")
.setMaxInstances(1)
.setMaxAttempts(10)
.build());
}
private RefreshOwnProfileJob(@NonNull Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
protected void onRun() throws Exception {
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self());
setProfileName(profile.getName());
setProfileAvatar(profile.getAvatar());
setProfileCapabilities(profile.getCapabilities());
}
@Override
protected boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() { }
private void setProfileName(@Nullable String encryptedName) {
try {
byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
String plaintextName = ProfileUtil.decryptName(profileKey, encryptedName);
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), plaintextName);
TextSecurePreferences.setProfileName(context, plaintextName);
} catch (InvalidCiphertextException | IOException e) {
Log.w(TAG, e);
}
}
private void setProfileAvatar(@Nullable String avatar) {
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), avatar));
}
private void setProfileCapabilities(@Nullable SignalServiceProfile.Capabilities capabilities) {
if (capabilities == null) {
return;
}
DatabaseFactory.getRecipientDatabase(context).setUuidSupported(Recipient.self().getId(), capabilities.isUuid());
}
public static final class Factory implements Job.Factory<RefreshOwnProfileJob> {
@Override
public @NonNull RefreshOwnProfileJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RefreshOwnProfileJob(parameters);
}
}
}

View File

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
@ -117,6 +118,10 @@ public class RetrieveProfileAvatarJob extends BaseJob {
}
database.setProfileAvatar(recipient.getId(), profileAvatar);
if (recipient.isLocalNumber()) {
TextSecurePreferences.setProfileAvatarId(context, Util.getSecureRandom().nextInt());
}
}
@Override

View File

@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
@ -39,6 +41,10 @@ import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulRespons
import java.io.IOException;
import java.util.List;
/**
* Retrieves a users profile and sets the appropriate local fields. If fetching the profile of the
* local user, use {@link RefreshOwnProfileJob} instead.
*/
public class RetrieveProfileJob extends BaseJob {
public static final String KEY = "RetrieveProfileJob";
@ -94,25 +100,12 @@ public class RetrieveProfileJob extends BaseJob {
}
private void handlePhoneNumberRecipient(Recipient recipient) throws IOException {
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(recipient);
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, recipient);
SignalServiceProfile profile;
try {
profile = retrieveProfile(address, unidentifiedAccess);
} catch (NonSuccessfulResponseCodeException e) {
if (unidentifiedAccess.isPresent()) {
profile = retrieveProfile(address, Optional.absent());
} else {
throw e;
}
}
setIdentityKey(recipient, profile.getIdentityKey());
setProfileName(recipient, profile.getName());
setProfileAvatar(recipient, profile.getAvatar());
setProfileCapabilities(recipient, profile.getCapabilities());
setIdentityKey(recipient, profile.getIdentityKey());
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
}
@ -124,26 +117,6 @@ public class RetrieveProfileJob extends BaseJob {
}
}
private SignalServiceProfile retrieveProfile(@NonNull SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
throws IOException
{
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
: authPipe;
if (pipe != null) {
try {
return pipe.getProfile(address, unidentifiedAccess);
} catch (IOException e) {
Log.w(TAG, e);
}
}
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
return receiver.retrieveProfile(address, unidentifiedAccess);
}
private void setIdentityKey(Recipient recipient, String identityKeyValue) {
try {
if (TextUtils.isEmpty(identityKeyValue)) {
@ -206,12 +179,7 @@ public class RetrieveProfileJob extends BaseJob {
byte[] profileKey = recipient.getProfileKey();
if (profileKey == null) return;
String plaintextProfileName = null;
if (profileName != null) {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName)));
}
String plaintextProfileName = ProfileUtil.decryptName(profileKey, profileName);
if (!Util.equals(plaintextProfileName, recipient.getProfileName())) {
DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient.getId(), plaintextProfileName);
@ -237,16 +205,6 @@ public class RetrieveProfileJob extends BaseJob {
DatabaseFactory.getRecipientDatabase(context).setUuidSupported(recipient.getId(), capabilities.isUuid());
}
private Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.absent();
}
public static final class Factory implements Job.Factory<RetrieveProfileJob> {
@Override

View File

@ -126,4 +126,9 @@ public final class LiveRecipientCache {
}
});
}
@AnyThread
public synchronized void clear() {
recipients.clear();
}
}

View File

@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.libsignal.util.guava.Preconditions;

View File

@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
@ -82,7 +83,7 @@ public class RecipientDetails {
this.profileName = isLocalNumber ? TextSecurePreferences.getProfileName(context) : settings.getProfileName();
this.defaultSubscriptionId = settings.getDefaultSubscriptionId();
this.registered = settings.getRegistered();
this.profileKey = settings.getProfileKey();
this.profileKey = isLocalNumber ? ProfileKeyUtil.getProfileKey(context) : settings.getProfileKey();
this.profileAvatar = settings.getProfileAvatar();
this.profileSharing = settings.isProfileSharing();
this.systemContact = systemContact;

View File

@ -0,0 +1,96 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import java.io.IOException;
/**
* Aids in the retrieval and decryption of profiles.
*/
public class ProfileUtil {
private static final String TAG = Log.tag(ProfileUtil.class);
@WorkerThread
public static SignalServiceProfile retrieveProfile(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(context, recipient);
SignalServiceProfile profile;
try {
profile = retrieveProfileInternal(address, unidentifiedAccess);
} catch (NonSuccessfulResponseCodeException e) {
if (unidentifiedAccess.isPresent()) {
profile = retrieveProfileInternal(address, Optional.absent());
} else {
throw e;
}
}
return profile;
}
public static @Nullable String decryptName(@NonNull byte[] profileKey, @Nullable String encryptedName)
throws InvalidCiphertextException, IOException
{
if (encryptedName == null) {
return null;
}
ProfileCipher profileCipher = new ProfileCipher(profileKey);
return new String(profileCipher.decryptName(Base64.decode(encryptedName)));
}
@WorkerThread
private static SignalServiceProfile retrieveProfileInternal(@NonNull SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
throws IOException
{
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
: authPipe;
if (pipe != null) {
try {
return pipe.getProfile(address, unidentifiedAccess);
} catch (IOException e) {
Log.w(TAG, e);
}
}
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
return receiver.retrieveProfile(address, unidentifiedAccess);
}
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.absent();
}
}

View File

@ -386,6 +386,10 @@ public class TextSecurePreferences {
return getBooleanPreference(context, LINK_PREVIEWS, true);
}
public static void setLinkPreviewsEnabled(Context context, boolean enabled) {
setBooleanPreference(context, LINK_PREVIEWS, enabled);
}
public static boolean isGifSearchInGridLayout(Context context) {
return getBooleanPreference(context, GIF_GRID_LAYOUT, false);
}
@ -607,6 +611,10 @@ public class TextSecurePreferences {
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
}
public static void setShowUnidentifiedDeliveryIndicatorsEnabled(Context context, boolean enabled) {
setBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled);
}
public static boolean isShowUnidentifiedDeliveryIndicatorsEnabled(Context context) {
return getBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, false);
}

View File

@ -375,11 +375,11 @@ dependencyVerification {
['org.whispersystems:signal-protocol-java:2.8.1',
'b19db36839ab008fdccefc7f8c005f2ea43dc7c7298a209bc424e6f9b6d5617b'],
['org.whispersystems:signal-service-android:2.15.1',
'27c6cf63393a2d466474e45162ec254c557f6a19e91962d0e4acc870373c41fd'],
['org.whispersystems:signal-service-android:2.15.3',
'24e7a7760f14a261d44b8d76fe6004157f7e09d7898c88ddb501ceabea47b6f6'],
['org.whispersystems:signal-service-java:2.15.1',
'd0023125e232b11ead150bbf38361fa61a3dbf3adc81c2473c673735380db9c2'],
['org.whispersystems:signal-service-java:2.15.3',
'bc6d58924daf7c15e700164ce602e2647260c41820a43837f3f48f3d5be2e963'],
['pl.tajchert:waitingdots:0.1.0',
'2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'],