Automatically recover from bad encrypted messages.

fork-5.53.8
Greyson Parrelli 2021-01-08 10:23:46 -05:00
rodzic adea15df10
commit 728f1707b6
22 zmienionych plików z 331 dodań i 51 usunięć

Wyświetl plik

@ -65,6 +65,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
implements SharedPreferences.OnSharedPreferenceChangeListener
{
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
@SuppressWarnings("unused")
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
@ -104,6 +105,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_BACKUPS_FRAGMENT, false)) {
initFragment(android.R.id.content, new BackupsPreferenceFragment());
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
initFragment(android.R.id.content, new HelpFragment());
} else if (icicle == null) {
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
} else {

Wyświetl plik

@ -61,6 +61,7 @@ public interface BindableConversationItem extends Unbindable {
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
void onDecryptionFailedLearnMoreClicked();
void onJoinGroupCallClicked();
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);

Wyświetl plik

@ -66,6 +66,7 @@ import org.signal.core.util.StreamUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
@ -1415,6 +1416,23 @@ public class ConversationFragment extends LoggingFragment {
GroupsV1MigrationInfoBottomSheetDialogFragment.show(requireFragmentManager(), membershipChange);
}
@Override
public void onDecryptionFailedLearnMoreClicked() {
new AlertDialog.Builder(requireContext())
.setView(R.layout.decryption_failed_dialog)
.setPositiveButton(android.R.string.ok, (d, w) -> {
d.dismiss();
})
.setNeutralButton(R.string.ConversationFragment_contact_us, (d, w) -> {
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_HELP_FRAGMENT, true);
startActivity(intent);
d.dismiss();
})
.show();
}
@Override
public void onJoinGroupCallClicked() {
CommunicationActions.startVideoCall(requireActivity(), recipient.get());

Wyświetl plik

@ -206,6 +206,16 @@ public final class ConversationUpdateItem extends LinearLayout
eventListener.onGroupMigrationLearnMoreClicked(conversationMessage.getMessageRecord().getGroupV1MigrationMembershipChanges());
}
});
} else if (conversationMessage.getMessageRecord().isFailedDecryptionType() &&
(!nextMessageRecord.isPresent() || !nextMessageRecord.get().isFailedDecryptionType()))
{
actionButton.setText(R.string.ConversationUpdateItem_learn_more);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {
if (batchSelected.isEmpty() && eventListener != null) {
eventListener.onDecryptionFailedLearnMoreClicked();
}
});
} else if (conversationMessage.getMessageRecord().isGroupCall()) {
UpdateDescription updateDescription = MessageRecord.getGroupCallUpdateDescription(getContext(), conversationMessage.getMessageRecord().getBody(), true);
Collection<UUID> uuids = updateDescription.getMentioned();

Wyświetl plik

@ -434,7 +434,8 @@ public final class ConversationListItem extends ConstraintLayout
} else if (SmsDatabase.Types.isKeyExchangeType(thread.getType())) {
return emphasisAdded(context, context.getString(R.string.ConversationListItem_key_exchange_message), defaultTint);
} else if (SmsDatabase.Types.isFailedDecryptType(thread.getType())) {
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_bad_encrypted_message), defaultTint);
UpdateDescription description = UpdateDescription.staticDescription(context.getString(R.string.ThreadRecord_chat_session_refreshed), R.drawable.ic_refresh_16);
return emphasisAdded(context, description, defaultTint);
} else if (SmsDatabase.Types.isNoRemoteSessionType(thread.getType())) {
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session), defaultTint);
} else if (SmsDatabase.Types.isEndSessionType(thread.getType())) {

Wyświetl plik

@ -29,4 +29,7 @@ public class SessionUtil {
new TextSecureSessionStore(context).archiveAllSessions();
}
public static void archiveSession(Context context, RecipientId recipientId, int deviceId) {
new TextSecureSessionStore(context).archiveSession(recipientId, deviceId);
}
}

Wyświetl plik

@ -103,6 +103,16 @@ public class TextSecureSessionStore implements SessionStore {
}
}
public void archiveSession(@NonNull RecipientId recipientId, int deviceId) {
synchronized (FILE_LOCK) {
SessionRecord session = DatabaseFactory.getSessionDatabase(context).load(recipientId, deviceId);
if (session != null) {
session.archiveCurrentState();
DatabaseFactory.getSessionDatabase(context).store(recipientId, deviceId, session);
}
}
}
public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) {
synchronized (FILE_LOCK) {
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {

Wyświetl plik

@ -149,6 +149,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved, String contentLocation, long threadId) throws MmsException;
public abstract Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId);
public abstract Optional<InsertResult> insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId) throws MmsException;
public abstract @NonNull InsertResult insertDecryptionFailedMessage(@NonNull RecipientId recipientId, long senderDeviceId, long sentTimestamp);
public abstract long insertMessageOutbox(long threadId, OutgoingTextMessage message, boolean forceSms, long date, InsertListener insertListener);
public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException;
public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, int defaultReceiptStatus, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException;

Wyświetl plik

@ -1383,6 +1383,11 @@ public class MmsDatabase extends MessageDatabase {
return new Pair<>(messageId, threadId);
}
@Override
public @NonNull InsertResult insertDecryptionFailedMessage(@NonNull RecipientId recipientId, long senderDeviceId, long sentTimestamp) {
throw new UnsupportedOperationException();
}
@Override
public void markIncomingNotificationReceived(long threadId) {
notifyConversationListeners(threadId);

Wyświetl plik

@ -1038,7 +1038,7 @@ public class SmsDatabase extends MessageDatabase {
if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
ContentValues values = new ContentValues(6);
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, message.getSender().serialize());
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
values.put(DATE_RECEIVED, System.currentTimeMillis());
@ -1093,6 +1093,36 @@ public class SmsDatabase extends MessageDatabase {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE);
}
@Override
public @NonNull InsertResult insertDecryptionFailedMessage(@NonNull RecipientId recipientId, long senderDeviceId, long sentTimestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.resolved(recipientId));
long type = Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
type = type & (Types.TOTAL_MASK - Types.ENCRYPTION_MASK) | Types.ENCRYPTION_REMOTE_FAILED_BIT;
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, recipientId.serialize());
values.put(ADDRESS_DEVICE_ID, senderDeviceId);
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, sentTimestamp);
values.put(DATE_SERVER, -1);
values.put(READ, 0);
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
long messageId = db.insert(TABLE_NAME, null, values);
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
ApplicationDependencies.getJobManager().add(new TrimThreadJob(threadId));
return new InsertResult(messageId, threadId);
}
@Override
public long insertMessageOutbox(long threadId, OutgoingTextMessage message,
boolean forceSms, long date, InsertListener insertListener)

Wyświetl plik

@ -191,6 +191,8 @@ public abstract class MessageRecord extends DisplayRecord {
else return fromRecipient(getIndividualRecipient(), r-> context.getString(R.string.SmsMessageRecord_secure_session_reset_s, r.getDisplayName(context)), R.drawable.ic_update_info_16);
} else if (isGroupV1MigrationEvent()) {
return getGroupMigrationEventDescription(context);
} else if (isFailedDecryptionType()) {
return staticUpdateDescription(context.getString(R.string.MessageRecord_chat_session_refreshed), R.drawable.ic_refresh_16);
}
return null;
@ -436,7 +438,7 @@ public abstract class MessageRecord extends DisplayRecord {
public boolean isUpdate() {
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() ||
isProfileChange() || isGroupV1MigrationEvent();
isProfileChange() || isGroupV1MigrationEvent() || isFailedDecryptionType();
}
public boolean isMediaPending() {
@ -471,6 +473,10 @@ public abstract class MessageRecord extends DisplayRecord {
return isFailed() && ((getRecipient().isPushGroup() && hasNetworkFailures()) || !isIdentityMismatchFailure());
}
public boolean isFailedDecryptionType() {
return MmsSmsColumns.Types.isFailedDecryptType(type);
}
protected static SpannableString emphasisAdded(String sequence) {
SpannableString spannable = new SpannableString(sequence);
spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Wyświetl plik

@ -66,7 +66,7 @@ public class SmsMessageRecord extends MessageRecord {
@Override
public SpannableString getDisplayBody(@NonNull Context context) {
if (SmsDatabase.Types.isFailedDecryptType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
return emphasisAdded(context.getString(R.string.MessageRecord_chat_session_refreshed));
} else if (isCorruptedKeyExchange()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_corrupted_key_exchange_message));
} else if (isInvalidVersionKeyExchange()) {

Wyświetl plik

@ -0,0 +1,126 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException;
/**
* - Archives the session associated with the specified device
* - Inserts an error message in the conversation
* - Sends a new, empty message to trigger a fresh session with the specified device
*
* This will only be run when all decryptions have finished, and there can only be one enqueued
* per websocket drain cycle.
*/
public class AutomaticSessionResetJob extends BaseJob {
private static final String TAG = Log.tag(AutomaticSessionResetJob.class);
public static final String KEY = "AutomaticSessionResetJob";
private static final String KEY_RECIPIENT_ID = "recipient_id";
private static final String KEY_DEVICE_ID = "device_id";
private static final String KEY_SENT_TIMESTAMP = "sent_timestamp";
private final RecipientId recipientId;
private final int deviceId;
private final long sentTimestamp;
public AutomaticSessionResetJob(@NonNull RecipientId recipientId, int deviceId, long sentTimestamp) {
this(new Parameters.Builder()
.setQueue(PushProcessMessageJob.getQueueName(recipientId))
.addConstraint(DecryptionsDrainedConstraint.KEY)
.setMaxInstancesForQueue(1)
.build(),
recipientId,
deviceId,
sentTimestamp);
}
private AutomaticSessionResetJob(@NonNull Parameters parameters,
@NonNull RecipientId recipientId,
int deviceId,
long sentTimestamp)
{
super(parameters);
this.recipientId = recipientId;
this.deviceId = deviceId;
this.sentTimestamp = sentTimestamp;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_RECIPIENT_ID, recipientId.serialize())
.putInt(KEY_DEVICE_ID, deviceId)
.putLong(KEY_SENT_TIMESTAMP, sentTimestamp)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
protected void onRun() throws Exception {
SessionUtil.archiveSession(context, recipientId, deviceId);
insertLocalMessage();
sendNullMessage();
}
@Override
protected boolean onShouldRetry(@NonNull Exception e) {
return e instanceof RetryLaterException;
}
@Override
public void onFailure() {
}
private void insertLocalMessage() {
MessageDatabase.InsertResult result = DatabaseFactory.getSmsDatabase(context).insertDecryptionFailedMessage(recipientId, deviceId, sentTimestamp);
ApplicationDependencies.getMessageNotifier().updateNotification(context, result.getThreadId());
}
private void sendNullMessage() throws IOException {
Recipient recipient = Recipient.resolved(recipientId);
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
try {
messageSender.sendNullMessage(address, unidentifiedAccess);
} catch (UntrustedIdentityException e) {
Log.w(TAG, "Unable to send null message.");
}
}
public static final class Factory implements Job.Factory<AutomaticSessionResetJob> {
@Override
public @NonNull AutomaticSessionResetJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new AutomaticSessionResetJob(parameters,
RecipientId.from(data.getString(KEY_RECIPIENT_ID)),
data.getInt(KEY_DEVICE_ID),
data.getLong(KEY_SENT_TIMESTAMP));
}
}
}

Wyświetl plik

@ -65,6 +65,7 @@ public final class JobManagerFactories {
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
put(AttachmentMarkUploadedJob.KEY, new AttachmentMarkUploadedJob.Factory());
put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory());
put(AutomaticSessionResetJob.KEY, new AutomaticSessionResetJob.Factory());
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());

Wyświetl plik

@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -179,22 +180,11 @@ public final class PushDecryptMessageJob extends BaseJob {
smsMessageId,
envelope.getTimestamp()));
} catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
} catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException | ProtocolNoSessionException e) {
Log.w(TAG, String.valueOf(envelope.getTimestamp()), e);
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.CORRUPT_MESSAGE,
toExceptionMetadata(e),
messageId,
smsMessageId,
envelope.getTimestamp()));
} catch (ProtocolNoSessionException e) {
Log.w(TAG, String.valueOf(envelope.getTimestamp()), e);
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.NO_SESSION,
toExceptionMetadata(e),
messageId,
smsMessageId,
envelope.getTimestamp()));
return Collections.singletonList(new AutomaticSessionResetJob(Recipient.external(context, e.getSender()).getId(),
e.getSenderDevice(),
envelope.getTimestamp()));
} catch (ProtocolLegacyMessageException e) {
Log.w(TAG, String.valueOf(envelope.getTimestamp()), e);
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.LEGACY_MESSAGE,

Wyświetl plik

@ -483,16 +483,6 @@ public final class PushProcessMessageJob extends BaseJob {
handleInvalidVersionMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
break;
case CORRUPT_MESSAGE:
warn(TAG, String.valueOf(timestamp), "Handling corrupt message.");
handleCorruptMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
break;
case NO_SESSION:
warn(TAG, String.valueOf(timestamp), "Handling no session.");
handleNoSessionMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
break;
case LEGACY_MESSAGE:
warn(TAG, String.valueOf(timestamp), "Handling legacy message.");
handleLegacyMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
@ -1474,23 +1464,6 @@ public final class PushProcessMessageJob extends BaseJob {
}
}
private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) {
smsDatabase.markAsNoSession(insertResult.get().getMessageId());
ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
}
}
private void handleUnsupportedDataMessage(@NonNull String sender,
int senderDevice,
@NonNull Optional<GroupId> groupId,
@ -2049,8 +2022,6 @@ public final class PushProcessMessageJob extends BaseJob {
public enum MessageState {
DECRYPTED_OK,
INVALID_VERSION,
CORRUPT_MESSAGE,
NO_SESSION,
LEGACY_MESSAGE,
DUPLICATE_MESSAGE,
UNSUPPORTED_DATA_MESSAGE

Wyświetl plik

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="110dp"
android:viewportWidth="200"
android:viewportHeight="110">
<path
android:pathData="M39.3,15.1L12.9,15C9.1,15 6,18.1 6,21.9L5.9,88C5.9,91.8 9,94.9 12.8,94.9L39.1,95C42.9,95 46,91.9 46,88.1L46.2,22C46.2,18.2 43.1,15.1 39.3,15.1ZM42.8,88.1C42.8,90.1 41.1,91.8 39.1,91.8L12.7,91.7C10.7,91.7 9,90 9,88L9.1,21.9C9.1,19.9 10.8,18.2 12.8,18.2L39.2,18.3C41.2,18.3 42.9,20 42.9,22L42.8,88.1Z"
android:fillColor="#C6C6C6"/>
<path
android:pathData="M187.3,15.1L160.9,15C157.1,15 154,18.1 154,21.9L153.9,88C153.9,91.8 157,94.9 160.8,94.9L187.2,95C191,95 194.1,91.9 194.1,88.1L194.2,22C194.2,18.2 191.1,15.1 187.3,15.1ZM190.8,88.1C190.8,90.1 189.1,91.8 187.1,91.8L160.7,91.7C158.7,91.7 157,90 157,88L157.1,21.9C157.1,19.9 158.8,18.2 160.8,18.2L187.2,18.3C189.2,18.3 190.9,20 190.9,22L190.8,88.1Z"
android:fillColor="#C6C6C6"/>
<path
android:pathData="M126,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M136,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M146,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M54,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M64,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M74,56m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#848484"/>
<path
android:pathData="M100,56m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#2C6BED"/>
<path
android:pathData="M109.38,56C109.393,58.503 108.413,60.909 106.655,62.69C104.896,64.471 102.503,65.481 100,65.5C98.443,65.479 96.916,65.076 95.551,64.327C94.187,63.577 93.028,62.504 92.175,61.201C91.323,59.898 90.804,58.406 90.664,56.855C90.524,55.305 90.767,53.744 91.372,52.31C91.977,50.875 92.925,49.611 94.133,48.629C95.341,47.647 96.772,46.977 98.3,46.678C99.828,46.379 101.405,46.46 102.895,46.913C104.384,47.367 105.739,48.18 106.84,49.28L107.84,50.28L107.49,48.98V46.56H109V52.5H103V51H105.39L106.81,51.38L105.81,50.38C104.695,49.252 103.27,48.481 101.716,48.164C100.162,47.848 98.549,48.001 97.082,48.603C95.615,49.205 94.36,50.23 93.476,51.547C92.593,52.864 92.121,54.414 92.12,56C92.12,57.035 92.324,58.06 92.72,59.015C93.116,59.972 93.696,60.84 94.428,61.572C95.16,62.304 96.028,62.884 96.984,63.28C97.94,63.676 98.965,63.88 100,63.88C101.035,63.88 102.059,63.676 103.016,63.28C103.972,62.884 104.84,62.304 105.572,61.572C106.304,60.84 106.884,59.972 107.28,59.015C107.676,58.06 107.88,57.035 107.88,56H109.38Z"
android:fillColor="#ffffff"/>
</vector>

Wyświetl plik

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="M13.46,8A5.46,5.46 0,1 1,2.55 8,5.54 5.54,0 0,1 12,4.06l0.44,0.44 -1.13,-0.33L10.17,4.17v1.5h4.49L14.66,1.18h-1.5L13.16,2.3l0.31,1.09L13.08,3a7,7 0,0 0,-12 5,7 7,0 0,0 7,7 7,7 0,0 0,7 -7ZM13.91,4.94Z"/>
</vector>

Wyświetl plik

@ -28,7 +28,7 @@
style="@style/Signal.Widget.Button.Small.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginTop="8dp"
android:visibility="gone"
tools:text="Learn more"
tools:visibility="visible" />

Wyświetl plik

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:importantForAccessibility="no"
app:srcCompat="@drawable/chat_session_refresh_banner" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/DecryptionFailedDialog_chat_session_refreshed"
style="@style/Signal.Text.Headline"
android:fontFamily="sans-serif-medium"
android:textSize="20sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/DecryptionFailedDialog_signal_uses_end_to_end_encryption"
style="@style/Signal.Text.Body"/>
</LinearLayout>

Wyświetl plik

@ -356,6 +356,7 @@
<string name="ConversationFragment__tap_to_review">Tap to review</string>
<string name="ConversationFragment__review_requests_carefully">Review requests carefully</string>
<string name="ConversationFragment__signal_found_another_contact_with_the_same_name">Signal found another contact with the same name.</string>
<string name="ConversationFragment_contact_us">Contact us</string>
<!-- ConversationListActivity -->
<string name="ConversationListActivity_there_is_no_browser_installed_on_your_device">There is no browser installed on your device.</string>
@ -454,6 +455,10 @@
<string name="DateUtils_today">Today</string>
<string name="DateUtils_yesterday">Yesterday</string>
<!-- DecryptionFailedDialog -->
<string name="DecryptionFailedDialog_chat_session_refreshed">Chat session refreshed</string>
<string name="DecryptionFailedDialog_signal_uses_end_to_end_encryption">Signal uses end-to-end encryption and it may need to refresh your chat session sometimes. This doesn\'t affect your chat\'s security, but you may have missed a message from this contact, and you can ask them to resend it.</string>
<!-- DeliveryStatus -->
<string name="DeliveryStatus_sending">Sending</string>
<string name="DeliveryStatus_sent">Sent</string>
@ -1080,6 +1085,7 @@
<string name="MessageRecord_disappearing_message_time_set_to_s">The disappearing message timer has been set to %1$s.</string>
<string name="MessageRecord_this_group_was_updated_to_a_new_group">This group was updated to a New Group.</string>
<string name="MessageRecord_you_couldnt_be_added_to_the_new_group_and_have_been_invited_to_join">You couldn\'t be added to the New Group and have been invited to join.</string>
<string name="MessageRecord_chat_session_refreshed">Chat session refreshed</string>
<plurals name="MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_invited">
<item quantity="one">A member couldn\'t be added to the New Group and has been invited to join.</item>
<item quantity="other">%1$s members couldn\'t be added to the New Group and have been invited to join.</item>
@ -1660,6 +1666,7 @@
<string name="ThreadRecord_contact">Contact</string>
<string name="ThreadRecord_file">File</string>
<string name="ThreadRecord_video">Video</string>
<string name="ThreadRecord_chat_session_refreshed">Chat session refreshed</string>
<!-- UpdateApkReadyListener -->
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
@ -1711,7 +1718,6 @@
<string name="AudioView_duration" translatable="false">%1$d:%2$02d</string>
<!-- MessageDisplayHelper -->
<string name="MessageDisplayHelper_bad_encrypted_message">Bad encrypted message</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Message encrypted for non-existing session</string>
<!-- MmsMessageRecord -->

Wyświetl plik

@ -275,6 +275,7 @@ public class SignalServiceMessageSender {
sendMessage(localAddress, Optional.<UnidentifiedAccess>absent(), timestamp, syncMessage, false, null);
}
// TODO [greyson][session] Delete this when we delete the button
if (message.isEndSession()) {
if (recipient.getUuid().isPresent()) {
store.deleteAllSessions(recipient.getUuid().get().toString());
@ -481,7 +482,6 @@ public class SignalServiceMessageSender {
attachment.getUploadTimestamp());
}
private void sendMessage(VerifiedMessage message, Optional<UnidentifiedAccessPair> unidentifiedAccess)
throws IOException, UntrustedIdentityException
{
@ -507,6 +507,26 @@ public class SignalServiceMessageSender {
}
}
public SendMessageResult sendNullMessage(SignalServiceAddress address, Optional<UnidentifiedAccessPair> unidentifiedAccess)
throws UntrustedIdentityException, IOException
{
byte[] nullMessageBody = DataMessage.newBuilder()
.setBody(Base64.encodeBytes(Util.getRandomLengthBytes(140)))
.build()
.toByteArray();
NullMessage nullMessage = NullMessage.newBuilder()
.setPadding(ByteString.copyFrom(nullMessageBody))
.build();
byte[] content = Content.newBuilder()
.setNullMessage(nullMessage)
.build()
.toByteArray();
return sendMessage(address, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), content, false, null);
}
private byte[] createTypingContent(SignalServiceTypingMessage message) {
Content.Builder container = Content.newBuilder();
TypingMessage.Builder builder = TypingMessage.newBuilder();