mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
Merge branch 'fix_profile_nulls' into open_groups_V2
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java # libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt
This commit is contained in:
commit
7f0962b3d4
@ -52,6 +52,8 @@ import androidx.viewpager.widget.ViewPager;
|
|||||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||||
import org.session.libsession.messaging.threads.Address;
|
import org.session.libsession.messaging.threads.Address;
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||||
@ -351,6 +353,12 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR,
|
saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR,
|
||||||
attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()]));
|
attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()]));
|
||||||
actionMode.finish();
|
actionMode.finish();
|
||||||
|
// Sending a Data extraction notification (for incoming attachments only)
|
||||||
|
boolean containsIncoming = mediaRecords.parallelStream().anyMatch(m -> !m.isOutgoing());
|
||||||
|
if (containsIncoming) {
|
||||||
|
//TODO uncomment line below when Data extraction will be activated
|
||||||
|
//sendMediaSavedNotificationIfNeeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
})
|
})
|
||||||
@ -358,6 +366,16 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
}, mediaRecords.size());
|
}, mediaRecords.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a MediaSaved notification to the recipient
|
||||||
|
*/
|
||||||
|
private void sendMediaSavedNotificationIfNeeded() {
|
||||||
|
// we don't send media saved notification for groups
|
||||||
|
if (recipient.isGroupRecipient()) return;
|
||||||
|
DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis()));
|
||||||
|
MessageSender.send(message, recipient.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void handleDeleteMedia(@NonNull Collection<MediaDatabase.MediaRecord> mediaRecords) {
|
private void handleDeleteMedia(@NonNull Collection<MediaDatabase.MediaRecord> mediaRecords) {
|
||||||
int recordCount = mediaRecords.size();
|
int recordCount = mediaRecords.size();
|
||||||
|
@ -39,7 +39,8 @@ import android.view.Window;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
@ -52,7 +53,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
|
||||||
import org.session.libsession.messaging.threads.Address;
|
import org.session.libsession.messaging.threads.Address;
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
@ -352,11 +352,26 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
saveTask.executeOnExecutor(
|
saveTask.executeOnExecutor(
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR,
|
AsyncTask.THREAD_POOL_EXECUTOR,
|
||||||
new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
||||||
|
// Sending a Data extraction notification (for incoming attachments only)
|
||||||
|
if(!mediaItem.outgoing) {
|
||||||
|
//TODO uncomment line below when Data extraction will be activated
|
||||||
|
//sendMediaSavedNotificationIfNeeded();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a MediaSaved notification to the recipient
|
||||||
|
*/
|
||||||
|
private void sendMediaSavedNotificationIfNeeded() {
|
||||||
|
// we don't send media saved notification for groups
|
||||||
|
if (conversationRecipient.isGroupRecipient()) return;
|
||||||
|
DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis()));
|
||||||
|
MessageSender.send(message, conversationRecipient.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void deleteMedia() {
|
private void deleteMedia() {
|
||||||
MediaItem mediaItem = getCurrentMediaItem();
|
MediaItem mediaItem = getCurrentMediaItem();
|
||||||
|
@ -177,6 +177,8 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
import org.thoughtcrime.securesms.mms.MmsException;
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage;
|
||||||
import org.thoughtcrime.securesms.mms.QuoteId;
|
import org.thoughtcrime.securesms.mms.QuoteId;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
@ -186,6 +188,9 @@ import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
|
||||||
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@ -803,15 +808,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime);
|
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime);
|
||||||
ExpirationTimerUpdate message = new ExpirationTimerUpdate(null, expirationTime);
|
ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime);
|
||||||
|
message.setRecipient(recipient.getAddress().serialize()); // we need the recipient in ExpiringMessageManager.insertOutgoingExpirationTimerMessage
|
||||||
message.setSentTimestamp(System.currentTimeMillis());
|
message.setSentTimestamp(System.currentTimeMillis());
|
||||||
OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient);
|
ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(getApplicationContext()).getExpiringMessageManager();
|
||||||
try {
|
expiringMessageManager.setExpirationTimer(message);
|
||||||
message.setId(DatabaseFactory.getMmsDatabase(ConversationActivity.this).insertMessageOutbox(outgoingMessage, getAllocatedThreadId(ConversationActivity.this), false, null));
|
|
||||||
MessageSender.send(message, recipient.getAddress());
|
MessageSender.send(message, recipient.getAddress());
|
||||||
} catch (MmsException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ import com.annimon.stream.Stream;
|
|||||||
|
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
|
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
|
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
|
||||||
|
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
|
||||||
import org.session.libsession.messaging.messages.visible.Quote;
|
import org.session.libsession.messaging.messages.visible.Quote;
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
|
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
|
||||||
@ -751,6 +752,11 @@ public class ConversationFragment extends Fragment
|
|||||||
if (!Util.isEmpty(attachments)) {
|
if (!Util.isEmpty(attachments)) {
|
||||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
||||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0]));
|
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0]));
|
||||||
|
// Sending a Data extraction notification (for incoming attachments only)
|
||||||
|
if(!message.isOutgoing()) {
|
||||||
|
//TODO uncomment line below when Data extraction will be activated
|
||||||
|
//sendMediaSavedNotificationIfNeeded();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,6 +769,16 @@ public class ConversationFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a MediaSaved notification to the recipient
|
||||||
|
*/
|
||||||
|
private void sendMediaSavedNotificationIfNeeded() {
|
||||||
|
// we don't send media saved notification for groups
|
||||||
|
if (recipient.isGroupRecipient()) return;
|
||||||
|
DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis()));
|
||||||
|
MessageSender.send(message, recipient.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
Log.i(TAG, "onCreateLoader");
|
Log.i(TAG, "onCreateLoader");
|
||||||
|
@ -14,11 +14,10 @@ import androidx.annotation.ColorInt;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage;
|
||||||
import org.thoughtcrime.securesms.BindableConversationItem;
|
import org.thoughtcrime.securesms.BindableConversationItem;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
|
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.GroupDescription;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
@ -106,6 +105,8 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
|
else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
|
||||||
else if (messageRecord.isJoined()) setJoinedRecord(messageRecord);
|
else if (messageRecord.isJoined()) setJoinedRecord(messageRecord);
|
||||||
else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord);
|
else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord);
|
||||||
|
else if (messageRecord.isScreenshotExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT);
|
||||||
|
else if (messageRecord.isMediaSavedExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED);
|
||||||
else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord);
|
else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord);
|
||||||
else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord);
|
else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord);
|
||||||
else if (messageRecord.isIdentityVerified() ||
|
else if (messageRecord.isIdentityVerified() ||
|
||||||
@ -149,6 +150,22 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
date.setVisibility(GONE);
|
date.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setDataExtractionRecord(final MessageRecord messageRecord, DataExtractionNotificationInfoMessage.Kind kind) {
|
||||||
|
@ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme());
|
||||||
|
if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) {
|
||||||
|
icon.setImageResource(R.drawable.quick_camera_dark);
|
||||||
|
} else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED) {
|
||||||
|
icon.setImageResource(R.drawable.ic_file_download_white_36dp);
|
||||||
|
}
|
||||||
|
icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
|
||||||
|
|
||||||
|
body.setText(messageRecord.getDisplayBody(getContext()));
|
||||||
|
|
||||||
|
title.setVisibility(VISIBLE);
|
||||||
|
body.setVisibility(VISIBLE);
|
||||||
|
date.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
private void setIdentityRecord(final MessageRecord messageRecord) {
|
private void setIdentityRecord(final MessageRecord messageRecord) {
|
||||||
icon.setImageResource(R.drawable.ic_security_white_24dp);
|
icon.setImageResource(R.drawable.ic_security_white_24dp);
|
||||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||||
@ -174,8 +191,6 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
private void setGroupRecord(MessageRecord messageRecord) {
|
private void setGroupRecord(MessageRecord messageRecord) {
|
||||||
icon.setImageResource(R.drawable.ic_group_grey600_24dp);
|
icon.setImageResource(R.drawable.ic_group_grey600_24dp);
|
||||||
icon.clearColorFilter();
|
icon.clearColorFilter();
|
||||||
|
|
||||||
GroupDescription.Companion.getDescription(getContext(), messageRecord.getBody()).addListener(this);
|
|
||||||
body.setText(messageRecord.getDisplayBody(getContext()));
|
body.setText(messageRecord.getDisplayBody(getContext()));
|
||||||
|
|
||||||
title.setVisibility(GONE);
|
title.setVisibility(GONE);
|
||||||
|
@ -514,15 +514,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
||||||
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts, previews);
|
|
||||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
|
||||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean expirationTimer = (outboxType & Types.EXPIRATION_TIMER_UPDATE_BIT) != 0;
|
|
||||||
|
|
||||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, expirationTimer, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
|
||||||
|
|
||||||
if (Types.isSecureType(outboxType)) {
|
if (Types.isSecureType(outboxType)) {
|
||||||
return new OutgoingSecureMediaMessage(message);
|
return new OutgoingSecureMediaMessage(message);
|
||||||
@ -532,8 +524,6 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new NoSuchMessageException("No record found for id: " + messageId);
|
throw new NoSuchMessageException("No record found for id: " + messageId);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new MmsException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null)
|
if (cursor != null)
|
||||||
cursor.close();
|
cursor.close();
|
||||||
@ -689,8 +679,19 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
{
|
{
|
||||||
if (threadId == -1) {
|
if (threadId == -1) {
|
||||||
if(retrieved.isGroup()) {
|
if(retrieved.isGroup()) {
|
||||||
ByteString decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupContext().getId();
|
String decodedGroupId;
|
||||||
String groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId.toByteArray());
|
if (retrieved instanceof OutgoingExpirationUpdateMessage) {
|
||||||
|
decodedGroupId = ((OutgoingExpirationUpdateMessage)retrieved).getGroupId();
|
||||||
|
} else {
|
||||||
|
decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupId();
|
||||||
|
}
|
||||||
|
String groupId;
|
||||||
|
try {
|
||||||
|
groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Couldn't encrypt group ID");
|
||||||
|
throw new MmsException(e);
|
||||||
|
}
|
||||||
Recipient group = Recipient.from(context, Address.fromSerialized(groupId), false);
|
Recipient group = Recipient.from(context, Address.fromSerialized(groupId), false);
|
||||||
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(group);
|
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(group);
|
||||||
} else {
|
} else {
|
||||||
@ -718,6 +719,14 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (retrieved.isScreenshotDataExtraction()) {
|
||||||
|
type |= Types.SCREENSHOT_EXTRACTION_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retrieved.isMediaSavedDataExtraction()) {
|
||||||
|
type |= Types.MEDIA_SAVED_EXTRACTION_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp);
|
return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,9 +754,8 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
|
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
|
||||||
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
|
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
|
||||||
|
|
||||||
if (message.isGroup()) {
|
if (message.isGroup() && message instanceof OutgoingGroupMediaMessage) {
|
||||||
if (((OutgoingGroupMediaMessage)message).isGroupUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
if (((OutgoingGroupMediaMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT;
|
||||||
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.isExpirationUpdate()) {
|
if (message.isExpirationUpdate()) {
|
||||||
|
@ -70,6 +70,11 @@ public interface MmsSmsColumns {
|
|||||||
protected static final long GROUP_UPDATE_BIT = 0x10000;
|
protected static final long GROUP_UPDATE_BIT = 0x10000;
|
||||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||||
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
|
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
|
||||||
|
protected static final long GROUP_UPDATE_MESSAGE_BIT = 0x80000;
|
||||||
|
|
||||||
|
// Data Extraction Information
|
||||||
|
protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000;
|
||||||
|
protected static final long SCREENSHOT_EXTRACTION_BIT = 0x02000;
|
||||||
|
|
||||||
// Encrypted Storage Information XXX
|
// Encrypted Storage Information XXX
|
||||||
public static final long ENCRYPTION_MASK = 0xFF000000;
|
public static final long ENCRYPTION_MASK = 0xFF000000;
|
||||||
@ -197,6 +202,14 @@ public interface MmsSmsColumns {
|
|||||||
return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0;
|
return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isMediaSavedExtraction(long type) {
|
||||||
|
return (type & MEDIA_SAVED_EXTRACTION_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isScreenshotExtraction(long type) {
|
||||||
|
return (type & SCREENSHOT_EXTRACTION_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isIncomingCall(long type) {
|
public static boolean isIncomingCall(long type) {
|
||||||
return type == INCOMING_CALL_TYPE;
|
return type == INCOMING_CALL_TYPE;
|
||||||
}
|
}
|
||||||
@ -213,6 +226,8 @@ public interface MmsSmsColumns {
|
|||||||
return (type & GROUP_UPDATE_BIT) != 0;
|
return (type & GROUP_UPDATE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isGroupUpdateMessage(long type) { return (type & GROUP_UPDATE_MESSAGE_BIT) != 0; }
|
||||||
|
|
||||||
public static boolean isGroupQuit(long type) {
|
public static boolean isGroupQuit(long type) {
|
||||||
return (type & GROUP_QUIT_BIT) != 0;
|
return (type & GROUP_QUIT_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
@ -347,8 +347,7 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
type |= Types.SECURE_MESSAGE_BIT;
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
} else if (message.isGroup()) {
|
} else if (message.isGroup()) {
|
||||||
type |= Types.SECURE_MESSAGE_BIT;
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
if (((IncomingGroupMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT;
|
||||||
else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
|
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
|
||||||
|
@ -17,11 +17,15 @@ import org.session.libsession.messaging.opengroups.OpenGroup
|
|||||||
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
|
import org.session.libsession.messaging.threads.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.messaging.threads.GroupRecord
|
import org.session.libsession.messaging.threads.GroupRecord
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageBuilder
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsession.utilities.IdentityKeyUtil
|
import org.session.libsession.utilities.IdentityKeyUtil
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -41,6 +45,7 @@ import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.get
|
import org.thoughtcrime.securesms.loki.utilities.get
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
|
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
|
||||||
override fun getUserPublicKey(): String? {
|
override fun getUserPublicKey(): String? {
|
||||||
@ -84,6 +89,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return recipient.profileKey
|
return recipient.profileKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDisplayNameForRecipient(recipientPublicKey: String): String? {
|
||||||
|
val database = DatabaseFactory.getLokiUserDatabase(context)
|
||||||
|
return database.getDisplayName(recipientPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) {
|
override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) {
|
||||||
val address = Address.fromSerialized(recipientPublicKey)
|
val address = Address.fromSerialized(recipientPublicKey)
|
||||||
val recipient = Recipient.from(context, address, false)
|
val recipient = Recipient.from(context, address, false)
|
||||||
@ -139,7 +149,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
||||||
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
||||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
val insertResult = if (message.sender == getUserPublicKey()) {
|
||||||
|
|
||||||
val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull())
|
val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull())
|
||||||
mmsDatabase.beginTransaction()
|
mmsDatabase.beginTransaction()
|
||||||
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!)
|
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!)
|
||||||
@ -457,33 +466,24 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
|
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
||||||
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
|
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
|
||||||
.setType(type0)
|
|
||||||
.setName(name)
|
|
||||||
.addAllMembers(members)
|
|
||||||
.addAllAdmins(admins)
|
|
||||||
val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
|
||||||
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
||||||
val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "")
|
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
|
||||||
|
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
|
||||||
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
||||||
smsDB.insertMessageInbox(infoMessage)
|
smsDB.insertMessageInbox(infoMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
|
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
|
||||||
val userPublicKey = getUserPublicKey()
|
val userPublicKey = getUserPublicKey()
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
|
||||||
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
|
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
|
||||||
.setType(type)
|
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
|
||||||
.setName(name)
|
|
||||||
.addAllMembers(members)
|
|
||||||
.addAllAdmins(admins)
|
|
||||||
val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, false, null, listOf(), listOf())
|
|
||||||
val mmsDB = DatabaseFactory.getMmsDatabase(context)
|
val mmsDB = DatabaseFactory.getMmsDatabase(context)
|
||||||
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)
|
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)
|
||||||
if (mmsSmsDB.getMessageFor(sentTimestamp,userPublicKey) != null) return
|
if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return
|
||||||
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null)
|
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null)
|
||||||
mmsDB.markAsSent(infoMessageID, true)
|
mmsDB.markAsSent(infoMessageID, true)
|
||||||
}
|
}
|
||||||
@ -652,4 +652,26 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
override fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri {
|
override fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri {
|
||||||
return PartAuthority.getAttachmentThumbnailUri(attachmentId)
|
return PartAuthority.getAttachmentThumbnailUri(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data Extraction Notification
|
||||||
|
override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) {
|
||||||
|
val database = DatabaseFactory.getMmsDatabase(context)
|
||||||
|
val address = fromSerialized(senderPublicKey)
|
||||||
|
val recipient = Recipient.from(context, address, false)
|
||||||
|
|
||||||
|
if (recipient.isBlocked) return
|
||||||
|
|
||||||
|
val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1,
|
||||||
|
0, false,
|
||||||
|
false,
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.of(message))
|
||||||
|
|
||||||
|
database.insertSecureDecryptedMessageInbox(mediaMessage, -1)
|
||||||
|
}
|
||||||
}
|
}
|
@ -109,6 +109,7 @@ public abstract class DisplayRecord {
|
|||||||
|
|
||||||
public boolean isLokiSessionRestoreDone() { return SmsDatabase.Types.isLokiSessionRestoreDoneType(type); }
|
public boolean isLokiSessionRestoreDone() { return SmsDatabase.Types.isLokiSessionRestoreDoneType(type); }
|
||||||
|
|
||||||
|
// TODO isGroupUpdate and isGroupQuit are kept for compatibility with old update messages, they can be removed later on
|
||||||
public boolean isGroupUpdate() {
|
public boolean isGroupUpdate() {
|
||||||
return SmsDatabase.Types.isGroupUpdate(type);
|
return SmsDatabase.Types.isGroupUpdate(type);
|
||||||
}
|
}
|
||||||
@ -117,14 +118,33 @@ public abstract class DisplayRecord {
|
|||||||
return SmsDatabase.Types.isGroupQuit(type);
|
return SmsDatabase.Types.isGroupQuit(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGroupUpdateMessage() {
|
||||||
|
return SmsDatabase.Types.isGroupUpdateMessage(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO isGroupAction can be replaced by isGroupUpdateMessage in the code when the 2 functions above are removed
|
||||||
public boolean isGroupAction() {
|
public boolean isGroupAction() {
|
||||||
return isGroupUpdate() || isGroupQuit();
|
return isGroupUpdate() || isGroupQuit() || isGroupUpdateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExpirationTimerUpdate() {
|
public boolean isExpirationTimerUpdate() {
|
||||||
return SmsDatabase.Types.isExpirationTimerUpdate(type);
|
return SmsDatabase.Types.isExpirationTimerUpdate(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data extraction
|
||||||
|
|
||||||
|
public boolean isMediaSavedExtraction() {
|
||||||
|
return MmsSmsColumns.Types.isMediaSavedExtraction(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isScreenshotExtraction() {
|
||||||
|
return MmsSmsColumns.Types.isScreenshotExtraction(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDataExtraction() {
|
||||||
|
return isMediaSavedExtraction() || isScreenshotExtraction();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCallLog() {
|
public boolean isCallLog() {
|
||||||
return SmsDatabase.Types.isCallLog(type);
|
return SmsDatabase.Types.isCallLog(type);
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,16 @@ import android.text.style.RelativeSizeSpan;
|
|||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage;
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageBuilder;
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageData;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.session.libsession.database.documents.IdentityKeyMismatch;
|
import org.session.libsession.database.documents.IdentityKeyMismatch;
|
||||||
import org.session.libsession.database.documents.NetworkFailure;
|
import org.session.libsession.database.documents.NetworkFailure;
|
||||||
|
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
import org.session.libsession.utilities.ExpirationUtil;
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.GroupDescription;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -90,39 +92,25 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
if (isGroupUpdate() && isOutgoing()) {
|
if(isGroupUpdateMessage()) {
|
||||||
|
UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody());
|
||||||
|
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing()));
|
||||||
|
} else if (isExpirationTimerUpdate()) {
|
||||||
|
int seconds = (int) (getExpiresIn() / 1000);
|
||||||
|
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing()));
|
||||||
|
} else if (isDataExtraction()) {
|
||||||
|
if (isScreenshotExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize())));
|
||||||
|
else if (isMediaSavedExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize())));
|
||||||
|
}
|
||||||
|
// TODO below lines are left here for compatibility with older group update messages, it can be deleted later on
|
||||||
|
else if (isGroupUpdate() && isOutgoing()) {
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
|
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
|
||||||
} else if (isGroupUpdate()) {
|
} else if (isGroupUpdate()) {
|
||||||
return new SpannableString(GroupDescription.Companion.getDescription(context, getBody()).toString(getIndividualRecipient()));
|
return new SpannableString(context.getString(R.string.MessageRecord_s_updated_group, getIndividualRecipient().toShortString()));
|
||||||
} else if (isGroupQuit() && isOutgoing()) {
|
} else if (isGroupQuit() && isOutgoing()) {
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
|
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
|
||||||
} else if (isGroupQuit()) {
|
} else if (isGroupQuit()) {
|
||||||
return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
|
return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
|
||||||
} else if (isIncomingCall()) {
|
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_s_called_you, getIndividualRecipient().toShortString()));
|
|
||||||
} else if (isOutgoingCall()) {
|
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_you_called));
|
|
||||||
} else if (isMissedCall()) {
|
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_missed_call));
|
|
||||||
} else if (isJoined()) {
|
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString()));
|
|
||||||
} else if (isExpirationTimerUpdate()) {
|
|
||||||
int seconds = (int)(getExpiresIn() / 1000);
|
|
||||||
if (seconds <= 0) {
|
|
||||||
return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_disabled_disappearing_messages))
|
|
||||||
: new SpannableString(context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, getIndividualRecipient().toShortString()));
|
|
||||||
}
|
|
||||||
String time = ExpirationUtil.getExpirationDisplayValue(context, seconds);
|
|
||||||
return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time))
|
|
||||||
: new SpannableString(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time));
|
|
||||||
} else if (isIdentityUpdate()) {
|
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString()));
|
|
||||||
} else if (isIdentityVerified()) {
|
|
||||||
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().toShortString()));
|
|
||||||
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().toShortString()));
|
|
||||||
} else if (isIdentityDefault()) {
|
|
||||||
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().toShortString()));
|
|
||||||
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().toShortString()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SpannableString(getBody());
|
return new SpannableString(getBody());
|
||||||
@ -175,7 +163,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
|
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || isDataExtraction() ||
|
||||||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent() || isLokiSessionRestoreDone();
|
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent() || isLokiSessionRestoreDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageBuilder;
|
||||||
|
import org.session.libsession.messaging.utilities.UpdateMessageData;
|
||||||
import org.session.libsession.utilities.ExpirationUtil;
|
import org.session.libsession.utilities.ExpirationUtil;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
@ -73,7 +75,7 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
Recipient recipient = getRecipient();
|
Recipient recipient = getRecipient();
|
||||||
if (isGroupUpdate()) {
|
if (isGroupUpdate() || isGroupUpdateMessage()) {
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
|
||||||
} else if (isGroupQuit()) {
|
} else if (isGroupQuit()) {
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group));
|
||||||
@ -103,12 +105,16 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
} else if (SmsDatabase.Types.isJoinedType(type)) {
|
} else if (SmsDatabase.Types.isJoinedType(type)) {
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, getRecipient().toShortString()));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, getRecipient().toShortString()));
|
||||||
} else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) {
|
} else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) {
|
||||||
int seconds = (int)(getExpiresIn() / 1000);
|
int seconds = (int) (getExpiresIn() / 1000);
|
||||||
if (seconds <= 0) {
|
if (seconds <= 0) {
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled));
|
||||||
}
|
}
|
||||||
String time = ExpirationUtil.getExpirationDisplayValue(context, seconds);
|
String time = ExpirationUtil.getExpirationDisplayValue(context, seconds);
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time));
|
||||||
|
} else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) {
|
||||||
|
return emphasisAdded(context.getString(R.string.ThreadRecord_media_saved_by_s, getRecipient().toShortString()));
|
||||||
|
} else if (MmsSmsColumns.Types.isScreenshotExtraction(type)) {
|
||||||
|
return emphasisAdded(context.getString(R.string.ThreadRecord_s_took_a_screenshot, getRecipient().toShortString()));
|
||||||
} else if (SmsDatabase.Types.isIdentityUpdate(type)) {
|
} else if (SmsDatabase.Types.isIdentityUpdate(type)) {
|
||||||
if (getRecipient().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed));
|
if (getRecipient().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed));
|
||||||
else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString()));
|
else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString()));
|
||||||
|
@ -32,6 +32,10 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
|
|||||||
set(newValue) { field = newValue; invalidateOptionsMenu() }
|
set(newValue) { field = newValue; invalidateOptionsMenu() }
|
||||||
private var members = listOf<String>()
|
private var members = listOf<String>()
|
||||||
set(value) { field = value; selectContactsAdapter.members = value }
|
set(value) { field = value; selectContactsAdapter.members = value }
|
||||||
|
private val publicKey: String
|
||||||
|
get() {
|
||||||
|
return TextSecurePreferences.getLocalNumber(this)!!
|
||||||
|
}
|
||||||
|
|
||||||
private val selectContactsAdapter by lazy {
|
private val selectContactsAdapter by lazy {
|
||||||
SelectContactsAdapter(this, GlideApp.with(this))
|
SelectContactsAdapter(this, GlideApp.with(this))
|
||||||
@ -72,7 +76,8 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun update(members: List<String>) {
|
private fun update(members: List<String>) {
|
||||||
this.members = members
|
//if there is a Note to self conversation, it loads self in the list, so we need to remove it here
|
||||||
|
this.members = members.minus(publicKey)
|
||||||
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
||||||
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
@ -36,6 +36,27 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli
|
|||||||
isSelected)
|
isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(viewHolder: ViewHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any>) {
|
||||||
|
if (payloads.isNotEmpty()) {
|
||||||
|
// Because these updates can be batched,
|
||||||
|
// there can be multiple payloads for a single bind
|
||||||
|
when (payloads[0]) {
|
||||||
|
Payload.MEMBER_CLICKED -> {
|
||||||
|
val member = members[position]
|
||||||
|
val isSelected = selectedMembers.contains(member)
|
||||||
|
viewHolder.view.toggleCheckbox(isSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When payload list is empty,
|
||||||
|
// or we don't have logic to handle a given type,
|
||||||
|
// default to full bind:
|
||||||
|
this.onBindViewHolder(viewHolder, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onMemberClick(member: String) {
|
private fun onMemberClick(member: String) {
|
||||||
if (selectedMembers.contains(member)) {
|
if (selectedMembers.contains(member)) {
|
||||||
selectedMembers.remove(member)
|
selectedMembers.remove(member)
|
||||||
@ -43,6 +64,11 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli
|
|||||||
selectedMembers.add(member)
|
selectedMembers.add(member)
|
||||||
}
|
}
|
||||||
val index = members.indexOf(member)
|
val index = members.indexOf(member)
|
||||||
notifyItemChanged(index)
|
notifyItemChanged(index, Payload.MEMBER_CLICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// define below the different events used to notify the adapter
|
||||||
|
enum class Payload {
|
||||||
|
MEMBER_CLICKED
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,11 +9,11 @@ import nl.komponents.kovenant.all
|
|||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
|
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller
|
||||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.service.loki.api.SnodeAPI
|
import org.session.libsignal.service.loki.api.SnodeAPI
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
|
|||||||
promises.addAll(privateChatsPromise.get())
|
promises.addAll(privateChatsPromise.get())
|
||||||
|
|
||||||
// Closed groups
|
// Closed groups
|
||||||
promises.addAll(ApplicationContext.getInstance(context).closedGroupPoller.pollOnce())
|
promises.addAll(ClosedGroupPoller().pollOnce())
|
||||||
|
|
||||||
// Open Groups
|
// Open Groups
|
||||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)->
|
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)->
|
||||||
|
@ -9,7 +9,6 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
|
|||||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
@ -104,11 +103,11 @@ object ClosedGroupsProtocolV2 {
|
|||||||
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
||||||
// Notify the user (if we didn't make the group)
|
// Notify the user (if we didn't make the group)
|
||||||
if (userPublicKey != senderPublicKey) {
|
if (userPublicKey != senderPublicKey) {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||||
} else if (prevGroup == null) {
|
} else if (prevGroup == null) {
|
||||||
// only notify if we created this group
|
// only notify if we created this group
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||||
}
|
}
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
@ -156,14 +155,14 @@ object ClosedGroupsProtocolV2 {
|
|||||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (contextType, signalType) =
|
val type =
|
||||||
if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT
|
if (senderLeft) SignalServiceGroup.Type.QUIT
|
||||||
else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE
|
else SignalServiceGroup.Type.UPDATE
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,9 +190,9 @@ object ClosedGroupsProtocolV2 {
|
|||||||
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
if (userPublicKey in admins) {
|
if (userPublicKey in admins) {
|
||||||
// send current encryption key to the latest added members
|
// send current encryption key to the latest added members
|
||||||
@ -230,9 +229,9 @@ object ClosedGroupsProtocolV2 {
|
|||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,9 +271,9 @@ object ClosedGroupsProtocolV2 {
|
|||||||
// Notify user
|
// Notify user
|
||||||
if (userLeft) {
|
if (userLeft) {
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,14 +384,13 @@ object ClosedGroupsProtocolV2 {
|
|||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val wasSenderRemoved = !members.contains(senderPublicKey)
|
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||||
val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
|
val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
||||||
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
|
||||||
val admins = group.admins.map { it.toString() }
|
val admins = group.admins.map { it.toString() }
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type0, name, members, admins, threadID, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins, sentTimestamp)
|
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.utilities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.session.libsession.messaging.threads.Address
|
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
|
||||||
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class GroupDescription(context: Context, groupContext: SignalServiceProtos.GroupContext?) {
|
|
||||||
private val context: Context
|
|
||||||
private val groupContext: SignalServiceProtos.GroupContext?
|
|
||||||
private val newMembers: MutableList<Recipient>
|
|
||||||
private val removedMembers: MutableList<Recipient>
|
|
||||||
private var wasCurrentUserRemoved: Boolean = false
|
|
||||||
private fun toRecipient(hexEncodedPublicKey: String): Recipient {
|
|
||||||
val address = Address.fromSerialized(hexEncodedPublicKey)
|
|
||||||
return Recipient.from(context, address, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toString(sender: Recipient): String {
|
|
||||||
if (wasCurrentUserRemoved) {
|
|
||||||
return context.getString(R.string.GroupUtil_you_were_removed_from_group)
|
|
||||||
}
|
|
||||||
val description = StringBuilder()
|
|
||||||
description.append(context.getString(R.string.MessageRecord_s_updated_group, sender.toShortString()))
|
|
||||||
if (groupContext == null) {
|
|
||||||
return description.toString()
|
|
||||||
}
|
|
||||||
val title = groupContext.name
|
|
||||||
if (!newMembers.isEmpty()) {
|
|
||||||
description.append("\n")
|
|
||||||
description.append(context.resources.getQuantityString(R.plurals.GroupUtil_joined_the_group,
|
|
||||||
newMembers.size, toString(newMembers)))
|
|
||||||
}
|
|
||||||
if (!removedMembers.isEmpty()) {
|
|
||||||
description.append("\n")
|
|
||||||
description.append(context.resources.getQuantityString(R.plurals.GroupUtil_removed_from_the_group,
|
|
||||||
removedMembers.size, toString(removedMembers)))
|
|
||||||
}
|
|
||||||
if (title != null && !title.trim { it <= ' ' }.isEmpty()) {
|
|
||||||
val separator = if (!newMembers.isEmpty() || !removedMembers.isEmpty()) " " else "\n"
|
|
||||||
description.append(separator)
|
|
||||||
description.append(context.getString(R.string.GroupUtil_group_name_is_now, title))
|
|
||||||
}
|
|
||||||
return description.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addListener(listener: RecipientModifiedListener?) {
|
|
||||||
if (!newMembers.isEmpty()) {
|
|
||||||
for (member in newMembers) {
|
|
||||||
member.addListener(listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toString(recipients: List<Recipient>): String {
|
|
||||||
var result = ""
|
|
||||||
for (i in recipients.indices) {
|
|
||||||
result += recipients[i].toShortString()
|
|
||||||
if (i != recipients.size - 1) result += ", "
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.context = context.applicationContext
|
|
||||||
this.groupContext = groupContext
|
|
||||||
newMembers = LinkedList()
|
|
||||||
removedMembers = LinkedList()
|
|
||||||
if (groupContext != null) {
|
|
||||||
val newMembers = groupContext.newMembersList
|
|
||||||
for (member in newMembers) {
|
|
||||||
this.newMembers.add(toRecipient(member))
|
|
||||||
}
|
|
||||||
val removedMembers = groupContext.removedMembersList
|
|
||||||
for (member in removedMembers) {
|
|
||||||
this.removedMembers.add(toRecipient(member))
|
|
||||||
}
|
|
||||||
wasCurrentUserRemoved = removedMembers.contains(TextSecurePreferences.getLocalNumber(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getDescription(context: Context, encodedGroup: String?): GroupDescription {
|
|
||||||
return if (encodedGroup == null) {
|
|
||||||
GroupDescription(context, null)
|
|
||||||
} else try {
|
|
||||||
val groupContext = SignalServiceProtos.GroupContext.parseFrom(Base64.decode(encodedGroup))
|
|
||||||
GroupDescription(context, groupContext)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.w("Loki", e)
|
|
||||||
GroupDescription(context, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -82,6 +82,11 @@ class UserView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleCheckbox(isSelected: Boolean = false) {
|
||||||
|
actionIndicatorImageView.visibility = View.VISIBLE
|
||||||
|
actionIndicatorImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
|
||||||
|
}
|
||||||
|
|
||||||
fun unbind() {
|
fun unbind() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,17 @@ package org.thoughtcrime.securesms.service;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage;
|
import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage;
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
|
|
||||||
import org.session.libsession.messaging.threads.Address;
|
import org.session.libsession.messaging.threads.Address;
|
||||||
import org.session.libsession.messaging.threads.DistributionTypes;
|
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
import org.session.libsession.utilities.GroupUtil;
|
import org.session.libsession.utilities.GroupUtil;
|
||||||
import org.session.libsession.utilities.SSKEnvironment;
|
import org.session.libsession.utilities.SSKEnvironment;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
@ -28,7 +23,6 @@ import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
|
|||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
import org.thoughtcrime.securesms.mms.MmsException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -79,8 +73,8 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
|||||||
String senderPublicKey = message.getSender();
|
String senderPublicKey = message.getSender();
|
||||||
|
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey.equals(senderPublicKey)) {
|
if (senderPublicKey == null || userPublicKey.equals(senderPublicKey)) {
|
||||||
// sender is a linked device
|
// sender is self or a linked device
|
||||||
insertOutgoingExpirationTimerMessage(message);
|
insertOutgoingExpirationTimerMessage(message);
|
||||||
} else {
|
} else {
|
||||||
insertIncomingExpirationTimerMessage(message);
|
insertIncomingExpirationTimerMessage(message);
|
||||||
@ -100,7 +94,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
|||||||
int duration = message.getDuration();
|
int duration = message.getDuration();
|
||||||
|
|
||||||
Optional<SignalServiceGroup> groupInfo = Optional.absent();
|
Optional<SignalServiceGroup> groupInfo = Optional.absent();
|
||||||
Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey);
|
Address address = Address.fromSerialized(senderPublicKey);
|
||||||
Recipient recipient = Recipient.from(context, address, false);
|
Recipient recipient = Recipient.from(context, address, false);
|
||||||
|
|
||||||
// if the sender is blocked, we don't display the update, except if it's in a closed group
|
// if the sender is blocked, we don't display the update, except if it's in a closed group
|
||||||
@ -123,6 +117,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
|||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
Optional.absent());
|
Optional.absent());
|
||||||
//insert the timer update message
|
//insert the timer update message
|
||||||
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||||
@ -138,41 +133,21 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
|||||||
private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) {
|
private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) {
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
|
||||||
String senderPublicKey = message.getSender();
|
|
||||||
Long sentTimestamp = message.getSentTimestamp();
|
Long sentTimestamp = message.getSentTimestamp();
|
||||||
String groupId = message.getGroupPublicKey();
|
String groupId = message.getGroupPublicKey();
|
||||||
int duration = message.getDuration();
|
int duration = message.getDuration();
|
||||||
|
|
||||||
Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey);
|
Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : message.getRecipient());
|
||||||
Recipient recipient = Recipient.from(context, address, false);
|
Recipient recipient = Recipient.from(context, address, false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId);
|
||||||
|
database.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp);
|
||||||
|
|
||||||
if (groupId != null) {
|
if (groupId != null) {
|
||||||
// conversation is a closed group
|
|
||||||
GroupContext groupContext = SignalServiceProtos.GroupContext.newBuilder()
|
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupId))).build();
|
|
||||||
OutgoingGroupMediaMessage infoMessage = new OutgoingGroupMediaMessage(recipient, groupContext, null, sentTimestamp, duration * 1000L, true, null, Collections.emptyList(), Collections.emptyList());
|
|
||||||
database.insertSecureDecryptedMessageOutbox(infoMessage, -1, sentTimestamp);
|
|
||||||
// we need the group ID as recipient for setExpireMessages below
|
// we need the group ID as recipient for setExpireMessages below
|
||||||
recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupId)), false);
|
recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupId)), false);
|
||||||
} else {
|
|
||||||
// conversation is a 1-1
|
|
||||||
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipient,
|
|
||||||
null,
|
|
||||||
Collections.emptyList(),
|
|
||||||
message.getSentTimestamp(),
|
|
||||||
-1,
|
|
||||||
duration * 1000L,
|
|
||||||
true,
|
|
||||||
DistributionTypes.DEFAULT,
|
|
||||||
null,
|
|
||||||
Collections.emptyList(),
|
|
||||||
Collections.emptyList(),
|
|
||||||
Collections.emptyList(),
|
|
||||||
Collections.emptyList());
|
|
||||||
database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, sentTimestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//set the timer to the conversation
|
//set the timer to the conversation
|
||||||
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration);
|
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration);
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.sskenvironment
|
|
||||||
|
|
||||||
class DataExtractionNotificationManager {
|
|
||||||
}
|
|
@ -418,6 +418,16 @@
|
|||||||
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Vous avez reçu un message chiffré avec une ancienne version de Signal qui n’est plus prise en charge. Veuillez demander à l’expéditeur de mettre Session à jour vers la version la plus récente et de renvoyer son message.</string>
|
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Vous avez reçu un message chiffré avec une ancienne version de Signal qui n’est plus prise en charge. Veuillez demander à l’expéditeur de mettre Session à jour vers la version la plus récente et de renvoyer son message.</string>
|
||||||
<string name="MessageRecord_left_group">Vous avez quitté le groupe</string>
|
<string name="MessageRecord_left_group">Vous avez quitté le groupe</string>
|
||||||
<string name="MessageRecord_you_updated_group">Vous avez mis le groupe à jour.</string>
|
<string name="MessageRecord_you_updated_group">Vous avez mis le groupe à jour.</string>
|
||||||
|
<string name="MessageRecord_you_created_a_new_group">Vous avez créée un nouveau groupe.</string>
|
||||||
|
<string name="MessageRecord_s_added_you_to_the_group">%1$s vous a ajouté au groupe.</string>
|
||||||
|
<string name="MessageRecord_you_renamed_the_group_to_s">Vous avez renommé le groupe en \'%1$s\'</string>
|
||||||
|
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s a renommé le groupe en \'%2$s\'</string>
|
||||||
|
<string name="MessageRecord_you_added_s_to_the_group">Vous avez ajouté %1$s au groupe.</string>
|
||||||
|
<string name="MessageRecord_s_added_s_to_the_group">%1$s a ajouté %2$s au groupe.</string>
|
||||||
|
<string name="MessageRecord_you_removed_s_from_the_group">Vous avez supprimé %1$s du groupe.</string>
|
||||||
|
<string name="MessageRecord_s_removed_s_from_the_group">%1$s a supprimé %2$s du groupe.</string>
|
||||||
|
<string name="MessageRecord_you_were_removed_from_the_group">Vous avez été supprimé du groupe.</string>
|
||||||
|
<string name="MessageRecord_you">Vous</string>
|
||||||
<string name="MessageRecord_you_called">Vous avez appelé</string>
|
<string name="MessageRecord_you_called">Vous avez appelé</string>
|
||||||
<string name="MessageRecord_called_you">Contact appelé</string>
|
<string name="MessageRecord_called_you">Contact appelé</string>
|
||||||
<string name="MessageRecord_missed_call">Appel manqué</string>
|
<string name="MessageRecord_missed_call">Appel manqué</string>
|
||||||
@ -430,6 +440,8 @@
|
|||||||
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s a désactivé les messages éphémères.</string>
|
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s a désactivé les messages éphémères.</string>
|
||||||
<string name="MessageRecord_you_set_disappearing_message_time_to_s">Vous avez défini l’expiration des messages éphémères à %1$s.</string>
|
<string name="MessageRecord_you_set_disappearing_message_time_to_s">Vous avez défini l’expiration des messages éphémères à %1$s.</string>
|
||||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s a défini l’expiration des messages éphémères à %2$s.</string>
|
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s a défini l’expiration des messages éphémères à %2$s.</string>
|
||||||
|
<string name="MessageRecord_s_took_a_screenshot">%1$s a pris une capture d’écran.</string>
|
||||||
|
<string name="MessageRecord_media_saved_by_s">%1$s a sauvegardé un media.</string>
|
||||||
<string name="MessageRecord_your_safety_number_with_s_has_changed">Votre numéro de sécurité avec %s a changé.</string>
|
<string name="MessageRecord_your_safety_number_with_s_has_changed">Votre numéro de sécurité avec %s a changé.</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">Vous avez marqué votre numéro de sécurité avec %s comme vérifié</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">Vous avez marqué votre numéro de sécurité avec %s comme vérifié</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">Vous avez marqué votre numéro de sécurité avec %s comme vérifié à partir d’un autre appareil</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">Vous avez marqué votre numéro de sécurité avec %s comme vérifié à partir d’un autre appareil</string>
|
||||||
@ -1483,4 +1495,35 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i
|
|||||||
<string name="fragment_contact_selection_closed_groups_title">Groupes privés</string>
|
<string name="fragment_contact_selection_closed_groups_title">Groupes privés</string>
|
||||||
<string name="fragment_contact_selection_open_groups_title">Groupes publics</string>
|
<string name="fragment_contact_selection_open_groups_title">Groupes publics</string>
|
||||||
|
|
||||||
|
<!-- Next round of translation -->
|
||||||
|
|
||||||
|
<string name="menu_apply_button">Appliquer</string>
|
||||||
|
<string name="menu_done_button">Terminé</string>
|
||||||
|
|
||||||
|
<string name="activity_edit_closed_group_title">Modifier le groupe</string>
|
||||||
|
<string name="activity_edit_closed_group_edit_text_hint">Saisissez un nouveau nom de groupe</string>
|
||||||
|
<string name="activity_edit_closed_group_edit_members">Membres</string>
|
||||||
|
<string name="activity_edit_closed_group_add_members">Ajouter des membres</string>
|
||||||
|
<string name="activity_edit_closed_group_group_name_missing_error">Le nom du groupe ne peut pas être vide</string>
|
||||||
|
<string name="activity_edit_closed_group_group_name_too_long_error">Veuillez saisir un nom plus court</string>
|
||||||
|
<string name="activity_edit_closed_group_not_enough_group_members_error">Les groupes doivent avoir au moins un membre</string>
|
||||||
|
<string name="activity_edit_closed_group_invalid_session_id_error">Un des membres du group a un Session ID invalide</string>
|
||||||
|
<string name="activity_edit_closed_group_confirm_removal">Êtes vous sûr de vouloir suprimer ce membre du groupe?</string>
|
||||||
|
<string name="activity_edit_closed_group_member_removed">Membre supprimé du groupe</string>
|
||||||
|
|
||||||
|
<string name="fragment_edit_group_bottom_sheet_remove">Supprimer le membre du groupe</string>
|
||||||
|
|
||||||
|
<string name="activity_select_contacts_title">Contacts</string>
|
||||||
|
|
||||||
|
<string name="dialog_ui_mode_title">Thème</string>
|
||||||
|
<string name="dialog_ui_mode_option_day">Jour</string>
|
||||||
|
<string name="dialog_ui_mode_option_night">Nuit</string>
|
||||||
|
<string name="dialog_ui_mode_option_system_default">Système</string>
|
||||||
|
|
||||||
|
<string name="activity_conversation_menu_copy_session_id">Copier le Session ID</string>
|
||||||
|
|
||||||
|
<string name="attachment">Pièce jointe</string>
|
||||||
|
<string name="attachment_type_voice_message">Message vocal</string>
|
||||||
|
<string name="details">Détails</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -493,6 +493,16 @@
|
|||||||
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message.</string>
|
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message.</string>
|
||||||
<string name="MessageRecord_left_group">You have left the group.</string>
|
<string name="MessageRecord_left_group">You have left the group.</string>
|
||||||
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
||||||
|
<string name="MessageRecord_you_created_a_new_group">You created a new group.</string>
|
||||||
|
<string name="MessageRecord_s_added_you_to_the_group">%1$s added you to the group.</string>
|
||||||
|
<string name="MessageRecord_you_renamed_the_group_to_s">You renamed the group to \'%1$s\'</string>
|
||||||
|
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s renamed the group to \'%2$s\'</string>
|
||||||
|
<string name="MessageRecord_you_added_s_to_the_group">You added %1$s to the group.</string>
|
||||||
|
<string name="MessageRecord_s_added_s_to_the_group">%1$s added %2$s to the group.</string>
|
||||||
|
<string name="MessageRecord_you_removed_s_from_the_group">You removed %1$s from the group.</string>
|
||||||
|
<string name="MessageRecord_s_removed_s_from_the_group">%1$s removed %2$s from the group.</string>
|
||||||
|
<string name="MessageRecord_you_were_removed_from_the_group">You were removed from the group.</string>
|
||||||
|
<string name="MessageRecord_you">You</string>
|
||||||
<string name="MessageRecord_you_called">You called</string>
|
<string name="MessageRecord_you_called">You called</string>
|
||||||
<string name="MessageRecord_called_you">Contact called</string>
|
<string name="MessageRecord_called_you">Contact called</string>
|
||||||
<string name="MessageRecord_missed_call">Missed call</string>
|
<string name="MessageRecord_missed_call">Missed call</string>
|
||||||
@ -505,6 +515,8 @@
|
|||||||
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
|
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
|
||||||
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s</string>
|
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s</string>
|
||||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s</string>
|
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s</string>
|
||||||
|
<string name="MessageRecord_s_took_a_screenshot">%1$s took a screenshot.</string>
|
||||||
|
<string name="MessageRecord_media_saved_by_s">Media saved by %1$s.</string>
|
||||||
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
||||||
@ -703,6 +715,8 @@
|
|||||||
<string name="ThreadRecord_s_is_on_signal">%s is on Session!</string>
|
<string name="ThreadRecord_s_is_on_signal">%s is on Session!</string>
|
||||||
<string name="ThreadRecord_disappearing_messages_disabled">Disappearing messages disabled</string>
|
<string name="ThreadRecord_disappearing_messages_disabled">Disappearing messages disabled</string>
|
||||||
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
||||||
|
<string name="ThreadRecord_s_took_a_screenshot">%s took a screenshot.</string>
|
||||||
|
<string name="ThreadRecord_media_saved_by_s">Media saved by %s.</string>
|
||||||
<string name="ThreadRecord_safety_number_changed">Safety number changed</string>
|
<string name="ThreadRecord_safety_number_changed">Safety number changed</string>
|
||||||
<string name="ThreadRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
<string name="ThreadRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||||
<string name="ThreadRecord_you_marked_verified">You marked verified</string>
|
<string name="ThreadRecord_you_marked_verified">You marked verified</string>
|
||||||
|
@ -12,6 +12,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage
|
|||||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||||
@ -35,6 +36,7 @@ interface StorageProtocol {
|
|||||||
fun setUserProfilePictureUrl(newProfilePicture: String)
|
fun setUserProfilePictureUrl(newProfilePicture: String)
|
||||||
|
|
||||||
fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray?
|
fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray?
|
||||||
|
fun getDisplayNameForRecipient(recipientPublicKey: String): String?
|
||||||
fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray)
|
fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray)
|
||||||
|
|
||||||
// Signal Protocol
|
// Signal Protocol
|
||||||
@ -118,9 +120,9 @@ interface StorageProtocol {
|
|||||||
fun removeClosedGroupPublicKey(groupPublicKey: String)
|
fun removeClosedGroupPublicKey(groupPublicKey: String)
|
||||||
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String)
|
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String)
|
||||||
fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String)
|
fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String)
|
||||||
fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type,
|
fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type,
|
||||||
name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long)
|
name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long)
|
||||||
fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String,
|
fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String,
|
||||||
members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long)
|
members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long)
|
||||||
fun isClosedGroup(publicKey: String): Boolean
|
fun isClosedGroup(publicKey: String): Boolean
|
||||||
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair>
|
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair>
|
||||||
@ -162,6 +164,9 @@ interface StorageProtocol {
|
|||||||
/// Returns the ID of the `TSIncomingMessage` that was constructed.
|
/// Returns the ID of the `TSIncomingMessage` that was constructed.
|
||||||
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
|
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
|
||||||
|
|
||||||
|
// Data Extraction Notification
|
||||||
|
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
|
||||||
|
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
fun getAuthToken(server: String): String?
|
fun getAuthToken(server: String): String?
|
||||||
fun setAuthToken(server: String, newValue: String?)
|
fun setAuthToken(server: String, newValue: String?)
|
||||||
|
@ -9,7 +9,7 @@ class DataExtractionNotification(): ControlMessage() {
|
|||||||
// Kind enum
|
// Kind enum
|
||||||
sealed class Kind {
|
sealed class Kind {
|
||||||
class Screenshot() : Kind()
|
class Screenshot() : Kind()
|
||||||
class MediaSaved(val timestanp: Long) : Kind()
|
class MediaSaved(val timestamp: Long) : Kind()
|
||||||
|
|
||||||
val description: String =
|
val description: String =
|
||||||
when(this) {
|
when(this) {
|
||||||
@ -46,7 +46,7 @@ class DataExtractionNotification(): ControlMessage() {
|
|||||||
val kind = kind ?: return false
|
val kind = kind ?: return false
|
||||||
return when(kind) {
|
return when(kind) {
|
||||||
is Kind.Screenshot -> true
|
is Kind.Screenshot -> true
|
||||||
is Kind.MediaSaved -> kind.timestanp > 0
|
is Kind.MediaSaved -> kind.timestamp > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class DataExtractionNotification(): ControlMessage() {
|
|||||||
is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT
|
is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT
|
||||||
is Kind.MediaSaved -> {
|
is Kind.MediaSaved -> {
|
||||||
dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED
|
dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED
|
||||||
dataExtractionNotification.timestamp = kind.timestanp
|
dataExtractionNotification.timestamp = kind.timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
@ -73,5 +73,4 @@ class DataExtractionNotification(): ControlMessage() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -33,6 +33,11 @@ class ExpirationTimerUpdate() : ControlMessage() {
|
|||||||
this.duration = duration
|
this.duration = duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal constructor(duration: Int) : this() {
|
||||||
|
this.syncTarget = null
|
||||||
|
this.duration = duration
|
||||||
|
}
|
||||||
|
|
||||||
// validation
|
// validation
|
||||||
override fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (!super.isValid()) return false
|
if (!super.isValid()) return false
|
||||||
|
@ -4,11 +4,13 @@ import static org.session.libsignal.service.internal.push.SignalServiceProtos.Gr
|
|||||||
|
|
||||||
public class IncomingGroupMessage extends IncomingTextMessage {
|
public class IncomingGroupMessage extends IncomingTextMessage {
|
||||||
|
|
||||||
private final GroupContext groupContext;
|
private final String groupID;
|
||||||
|
private final boolean updateMessage;
|
||||||
|
|
||||||
public IncomingGroupMessage(IncomingTextMessage base, GroupContext groupContext, String body) {
|
public IncomingGroupMessage(IncomingTextMessage base, String groupID, String body, boolean updateMessage) {
|
||||||
super(base, body);
|
super(base, body);
|
||||||
this.groupContext = groupContext;
|
this.groupID = groupID;
|
||||||
|
this.updateMessage = updateMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -16,12 +18,6 @@ public class IncomingGroupMessage extends IncomingTextMessage {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdate() {
|
public boolean isUpdateMessage() { return updateMessage; }
|
||||||
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isQuit() {
|
|
||||||
return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,11 @@ package org.session.libsession.messaging.messages.signal;
|
|||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
|
||||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage;
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
|
||||||
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
||||||
import org.session.libsession.messaging.threads.Address;
|
import org.session.libsession.messaging.threads.Address;
|
||||||
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||||
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||||
import org.session.libsession.utilities.GroupUtil;
|
import org.session.libsession.utilities.GroupUtil;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
||||||
@ -26,9 +27,11 @@ public class IncomingMediaMessage {
|
|||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
private final boolean expirationUpdate;
|
private final boolean expirationUpdate;
|
||||||
private final QuoteModel quote;
|
|
||||||
private final boolean unidentified;
|
private final boolean unidentified;
|
||||||
|
|
||||||
|
private final DataExtractionNotificationInfoMessage dataExtractionNotification;
|
||||||
|
private final QuoteModel quote;
|
||||||
|
|
||||||
private final List<Attachment> attachments = new LinkedList<>();
|
private final List<Attachment> attachments = new LinkedList<>();
|
||||||
private final List<Contact> sharedContacts = new LinkedList<>();
|
private final List<Contact> sharedContacts = new LinkedList<>();
|
||||||
private final List<LinkPreview> linkPreviews = new LinkedList<>();
|
private final List<LinkPreview> linkPreviews = new LinkedList<>();
|
||||||
@ -44,7 +47,8 @@ public class IncomingMediaMessage {
|
|||||||
Optional<List<SignalServiceAttachment>> attachments,
|
Optional<List<SignalServiceAttachment>> attachments,
|
||||||
Optional<QuoteModel> quote,
|
Optional<QuoteModel> quote,
|
||||||
Optional<List<Contact>> sharedContacts,
|
Optional<List<Contact>> sharedContacts,
|
||||||
Optional<List<LinkPreview>> linkPreviews)
|
Optional<List<LinkPreview>> linkPreviews,
|
||||||
|
Optional<DataExtractionNotificationInfoMessage> dataExtractionNotification)
|
||||||
{
|
{
|
||||||
this.push = true;
|
this.push = true;
|
||||||
this.from = from;
|
this.from = from;
|
||||||
@ -53,6 +57,7 @@ public class IncomingMediaMessage {
|
|||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
this.expirationUpdate = expirationUpdate;
|
this.expirationUpdate = expirationUpdate;
|
||||||
|
this.dataExtractionNotification = dataExtractionNotification.orNull();
|
||||||
this.quote = quote.orNull();
|
this.quote = quote.orNull();
|
||||||
this.unidentified = unidentified;
|
this.unidentified = unidentified;
|
||||||
|
|
||||||
@ -73,7 +78,7 @@ public class IncomingMediaMessage {
|
|||||||
Optional<List<LinkPreview>> linkPreviews)
|
Optional<List<LinkPreview>> linkPreviews)
|
||||||
{
|
{
|
||||||
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false,
|
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false,
|
||||||
false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews);
|
false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSubscriptionId() {
|
public int getSubscriptionId() {
|
||||||
@ -116,6 +121,20 @@ public class IncomingMediaMessage {
|
|||||||
return groupId != null;
|
return groupId != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isScreenshotDataExtraction() {
|
||||||
|
if (dataExtractionNotification == null) return false;
|
||||||
|
else {
|
||||||
|
return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMediaSavedDataExtraction() {
|
||||||
|
if (dataExtractionNotification == null) return false;
|
||||||
|
else {
|
||||||
|
return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public QuoteModel getQuote() {
|
public QuoteModel getQuote() {
|
||||||
return quote;
|
return quote;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.session.libsession.messaging.messages.signal;
|
package org.session.libsession.messaging.messages.signal;
|
||||||
|
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||||
import org.session.libsession.messaging.threads.DistributionTypes;
|
import org.session.libsession.messaging.threads.DistributionTypes;
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
@ -10,15 +9,13 @@ import java.util.LinkedList;
|
|||||||
|
|
||||||
public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage {
|
public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage {
|
||||||
|
|
||||||
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) {
|
private final String groupId;
|
||||||
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
|
|
||||||
DistributionTypes.CONVERSATION, expiresIn, true, null, Collections.emptyList(),
|
|
||||||
Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OutgoingExpirationUpdateMessage from(ExpirationTimerUpdate message,
|
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, String groupId) {
|
||||||
Recipient recipient) {
|
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
|
||||||
return new OutgoingExpirationUpdateMessage(recipient, message.getSentTimestamp(), message.getDuration() * 1000);
|
DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(),
|
||||||
|
Collections.emptyList());
|
||||||
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -26,4 +23,13 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGroup() {
|
||||||
|
return groupId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,56 +19,27 @@ import java.util.List;
|
|||||||
|
|
||||||
public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||||
|
|
||||||
private final GroupContext group;
|
private final String groupID;
|
||||||
|
private final boolean isUpdateMessage;
|
||||||
|
|
||||||
public OutgoingGroupMediaMessage(@NonNull Recipient recipient,
|
public OutgoingGroupMediaMessage(@NonNull Recipient recipient,
|
||||||
@NonNull String encodedGroupContext,
|
@NonNull String body,
|
||||||
@NonNull List<Attachment> avatar,
|
@Nullable String groupId,
|
||||||
long sentTimeMillis,
|
|
||||||
long expiresIn,
|
|
||||||
@Nullable QuoteModel quote,
|
|
||||||
@NonNull List<Contact> contacts,
|
|
||||||
@NonNull List<LinkPreview> previews)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
super(recipient, encodedGroupContext, avatar, sentTimeMillis,
|
|
||||||
DistributionTypes.CONVERSATION, expiresIn, false, quote, contacts, previews);
|
|
||||||
|
|
||||||
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutgoingGroupMediaMessage(@NonNull Recipient recipient,
|
|
||||||
@NonNull GroupContext group,
|
|
||||||
@Nullable final Attachment avatar,
|
|
||||||
long expireIn,
|
|
||||||
@Nullable QuoteModel quote,
|
|
||||||
@NonNull List<Contact> contacts,
|
|
||||||
@NonNull List<LinkPreview> previews)
|
|
||||||
{
|
|
||||||
super(recipient, Base64.encodeBytes(group.toByteArray()),
|
|
||||||
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
DistributionTypes.CONVERSATION, expireIn, false, quote, contacts, previews);
|
|
||||||
|
|
||||||
this.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutgoingGroupMediaMessage(@NonNull Recipient recipient,
|
|
||||||
@NonNull GroupContext group,
|
|
||||||
@Nullable final Attachment avatar,
|
@Nullable final Attachment avatar,
|
||||||
long sentTime,
|
long sentTime,
|
||||||
long expireIn,
|
long expireIn,
|
||||||
boolean expirationUpdate,
|
boolean updateMessage,
|
||||||
@Nullable QuoteModel quote,
|
@Nullable QuoteModel quote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> previews)
|
@NonNull List<LinkPreview> previews)
|
||||||
{
|
{
|
||||||
super(recipient, Base64.encodeBytes(group.toByteArray()),
|
super(recipient, body,
|
||||||
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
||||||
sentTime,
|
sentTime,
|
||||||
DistributionTypes.CONVERSATION, expireIn, expirationUpdate, quote, contacts, previews);
|
DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews);
|
||||||
|
|
||||||
this.group = group;
|
this.groupID = groupId;
|
||||||
|
this.isUpdateMessage = updateMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,15 +47,11 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGroupUpdate() {
|
public String getGroupId() {
|
||||||
return group.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
return groupID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGroupQuit() {
|
public boolean isUpdateMessage() {
|
||||||
return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
|
return isUpdateMessage;
|
||||||
}
|
|
||||||
|
|
||||||
public GroupContext getGroupContext() {
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ public class OutgoingMediaMessage {
|
|||||||
private final int distributionType;
|
private final int distributionType;
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
private final boolean expirationUpdate;
|
|
||||||
private final QuoteModel outgoingQuote;
|
private final QuoteModel outgoingQuote;
|
||||||
|
|
||||||
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
||||||
@ -37,7 +36,6 @@ public class OutgoingMediaMessage {
|
|||||||
public OutgoingMediaMessage(Recipient recipient, String message,
|
public OutgoingMediaMessage(Recipient recipient, String message,
|
||||||
List<Attachment> attachments, long sentTimeMillis,
|
List<Attachment> attachments, long sentTimeMillis,
|
||||||
int subscriptionId, long expiresIn,
|
int subscriptionId, long expiresIn,
|
||||||
boolean expirationUpdate,
|
|
||||||
int distributionType,
|
int distributionType,
|
||||||
@Nullable QuoteModel outgoingQuote,
|
@Nullable QuoteModel outgoingQuote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@ -52,7 +50,6 @@ public class OutgoingMediaMessage {
|
|||||||
this.attachments = attachments;
|
this.attachments = attachments;
|
||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
this.expirationUpdate = expirationUpdate;
|
|
||||||
this.outgoingQuote = outgoingQuote;
|
this.outgoingQuote = outgoingQuote;
|
||||||
|
|
||||||
this.contacts.addAll(contacts);
|
this.contacts.addAll(contacts);
|
||||||
@ -69,7 +66,6 @@ public class OutgoingMediaMessage {
|
|||||||
this.sentTimeMillis = that.sentTimeMillis;
|
this.sentTimeMillis = that.sentTimeMillis;
|
||||||
this.subscriptionId = that.subscriptionId;
|
this.subscriptionId = that.subscriptionId;
|
||||||
this.expiresIn = that.expiresIn;
|
this.expiresIn = that.expiresIn;
|
||||||
this.expirationUpdate = that.expirationUpdate;
|
|
||||||
this.outgoingQuote = that.outgoingQuote;
|
this.outgoingQuote = that.outgoingQuote;
|
||||||
|
|
||||||
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
|
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
|
||||||
@ -89,7 +85,7 @@ public class OutgoingMediaMessage {
|
|||||||
previews = Collections.singletonList(linkPreview);
|
previews = Collections.singletonList(linkPreview);
|
||||||
}
|
}
|
||||||
return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1,
|
return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1,
|
||||||
recipient.getExpireMessages() * 1000, false, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(),
|
recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(),
|
||||||
previews, Collections.emptyList(), Collections.emptyList());
|
previews, Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +109,7 @@ public class OutgoingMediaMessage {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExpirationUpdate() { return expirationUpdate; }
|
public boolean isExpirationUpdate() { return false; }
|
||||||
|
|
||||||
public long getSentTimeMillis() {
|
public long getSentTimeMillis() {
|
||||||
return sentTimeMillis;
|
return sentTimeMillis;
|
||||||
|
@ -19,12 +19,11 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
|||||||
long sentTimeMillis,
|
long sentTimeMillis,
|
||||||
int distributionType,
|
int distributionType,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
boolean expirationUpdate,
|
|
||||||
@Nullable QuoteModel quote,
|
@Nullable QuoteModel quote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> previews)
|
@NonNull List<LinkPreview> previews)
|
||||||
{
|
{
|
||||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expirationUpdate, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
|
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
||||||
|
@ -9,6 +9,7 @@ import org.session.libsession.messaging.messages.control.*
|
|||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||||
@ -45,6 +46,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
|||||||
is TypingIndicator -> handleTypingIndicator(message)
|
is TypingIndicator -> handleTypingIndicator(message)
|
||||||
is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message)
|
is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message)
|
||||||
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
||||||
|
is DataExtractionNotification -> handleDataExtractionNotification(message)
|
||||||
is ConfigurationMessage -> handleConfigurationMessage(message)
|
is ConfigurationMessage -> handleConfigurationMessage(message)
|
||||||
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
|
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
|
||||||
}
|
}
|
||||||
@ -91,7 +93,25 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMessage) {
|
// Data Extraction Notification handling
|
||||||
|
|
||||||
|
private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) {
|
||||||
|
// we don't handle data extraction messages for groups (they shouldn't be sent, but in case we filter them here too)
|
||||||
|
if (message.groupPublicKey != null) return
|
||||||
|
|
||||||
|
val storage = MessagingConfiguration.shared.storage
|
||||||
|
val senderPublicKey = message.sender!!
|
||||||
|
val notification: DataExtractionNotificationInfoMessage = when(message.kind) {
|
||||||
|
is DataExtractionNotification.Kind.Screenshot -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.SCREENSHOT)
|
||||||
|
is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED)
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration message handling
|
||||||
|
|
||||||
|
private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
||||||
val context = MessagingConfiguration.shared.context
|
val context = MessagingConfiguration.shared.context
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return
|
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return
|
||||||
@ -130,32 +150,31 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes
|
|||||||
fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
val context = MessagingConfiguration.shared.context
|
val context = MessagingConfiguration.shared.context
|
||||||
|
val userPublicKey = storage.getUserPublicKey()
|
||||||
// Update profile if needed
|
// Update profile if needed
|
||||||
val newProfile = message.profile
|
val newProfile = message.profile
|
||||||
if (newProfile != null) {
|
if (newProfile != null && openGroupID.isNullOrEmpty() && userPublicKey != message.sender) {
|
||||||
val profileManager = SSKEnvironment.shared.profileManager
|
val profileManager = SSKEnvironment.shared.profileManager
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
|
||||||
val displayName = newProfile.displayName!!
|
val displayName = newProfile.displayName!!
|
||||||
val userPublicKey = storage.getUserPublicKey()
|
if (displayName.isNotEmpty()) {
|
||||||
if (openGroupID == null) {
|
|
||||||
if (userPublicKey == message.sender) {
|
|
||||||
// Update the user's local name if the message came from their master device
|
|
||||||
TextSecurePreferences.setProfileName(context, displayName)
|
|
||||||
}
|
|
||||||
profileManager.setDisplayName(context, recipient, displayName)
|
profileManager.setDisplayName(context, recipient, displayName)
|
||||||
}
|
}
|
||||||
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) {
|
if (newProfile.profileKey?.isNotEmpty() == true && !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) {
|
||||||
profileManager.setProfileKey(context, recipient, newProfile.profileKey!!)
|
profileManager.setProfileKey(context, recipient, newProfile.profileKey!!)
|
||||||
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
||||||
val url = newProfile.profilePictureURL.orEmpty()
|
val newUrl = newProfile.profilePictureURL
|
||||||
profileManager.setProfilePictureURL(context, recipient, url)
|
if (!newUrl.isNullOrEmpty()) {
|
||||||
|
profileManager.setProfilePictureURL(context, recipient, newUrl)
|
||||||
if (userPublicKey == message.sender) {
|
if (userPublicKey == message.sender) {
|
||||||
profileManager.updateOpenGroupProfilePicturesIfNeeded(context)
|
profileManager.updateOpenGroupProfilePicturesIfNeeded(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Get or create thread
|
// Get or create thread
|
||||||
val threadID = storage.getOrCreateThreadIdFor(message.syncTarget ?: message.sender!!, message.groupPublicKey, openGroupID)
|
val threadID = storage.getOrCreateThreadIdFor(message.syncTarget
|
||||||
|
?: message.sender!!, message.groupPublicKey, openGroupID)
|
||||||
// Parse quote if needed
|
// Parse quote if needed
|
||||||
var quoteModel: QuoteModel? = null
|
var quoteModel: QuoteModel? = null
|
||||||
if (message.quote != null && proto.dataMessage.hasQuote()) {
|
if (message.quote != null && proto.dataMessage.hasQuote()) {
|
||||||
@ -245,8 +264,15 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
|||||||
} else {
|
} else {
|
||||||
storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
|
storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
|
||||||
null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp)
|
null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp)
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
if (userPublicKey == sender) {
|
||||||
|
// sender is a linked device
|
||||||
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp)
|
||||||
|
} else {
|
||||||
|
storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
storage.setProfileSharing(Address.fromSerialized(groupID), true)
|
storage.setProfileSharing(Address.fromSerialized(groupID), true)
|
||||||
// Add the group to the user's set of public keys to poll for
|
// Add the group to the user's set of public keys to poll for
|
||||||
@ -306,9 +332,8 @@ private fun MessageReceiver.handleClosedGroupUpdated(message: ClosedGroupControl
|
|||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val wasSenderRemoved = !members.contains(senderPublicKey)
|
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||||
val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED
|
||||||
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!)
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) {
|
private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) {
|
||||||
@ -380,9 +405,9 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon
|
|||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
// sender is a linked device
|
// sender is a linked device
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, threadID, message.sentTimestamp!!)
|
||||||
} else {
|
} else {
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!)
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,9 +440,9 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
|
|||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
// sender is a linked device
|
// sender is a linked device
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, threadID, message.sentTimestamp!!)
|
||||||
} else {
|
} else {
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!)
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
if (userPublicKey in admins) {
|
if (userPublicKey in admins) {
|
||||||
// send current encryption key to the latest added members
|
// send current encryption key to the latest added members
|
||||||
@ -479,17 +504,16 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
|||||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (contextType, signalType) =
|
val type = if (senderLeft) SignalServiceGroup.Type.QUIT
|
||||||
if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT
|
else SignalServiceGroup.Type.MEMBER_REMOVED
|
||||||
else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE
|
|
||||||
|
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
// sender is a linked device
|
// sender is a linked device
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, message.sentTimestamp!!)
|
storage.insertOutgoingInfoMessage(context, groupID, type, name, updateMembers, admins, threadID, message.sentTimestamp!!)
|
||||||
} else {
|
} else {
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!)
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, updateMembers, admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,9 +559,9 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
|
|||||||
if (userLeft) {
|
if (userLeft) {
|
||||||
//sender is a linked device
|
//sender is a linked device
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.QUIT, name, members, admins, threadID, message.sentTimestamp!!)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, message.sentTimestamp!!)
|
||||||
} else {
|
} else {
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!)
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
|||||||
import org.session.libsignal.libsignal.ecc.Curve
|
import org.session.libsignal.libsignal.ecc.Curve
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional
|
import org.session.libsignal.libsignal.util.guava.Optional
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
@ -60,7 +61,7 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
|||||||
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
// Fulfill the promise
|
// Fulfill the promise
|
||||||
@ -107,7 +108,7 @@ fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
|||||||
// Update the group
|
// Update the group
|
||||||
storage.updateTitle(groupID, newName)
|
storage.updateTitle(groupID, newName)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE
|
val infoType = SignalServiceGroup.Type.NAME_CHANGE
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime)
|
||||||
}
|
}
|
||||||
@ -150,9 +151,9 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>)
|
|||||||
send(closedGroupControlMessage, Address.fromSerialized(member))
|
send(closedGroupControlMessage, Address.fromSerialized(member))
|
||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE
|
val infoType = SignalServiceGroup.Type.MEMBER_ADDED
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToAdd, admins, threadID, sentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<String>) {
|
fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<String>) {
|
||||||
@ -189,9 +190,9 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
|
|||||||
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE
|
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToRemove, admins, threadID, sentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
|
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
|
||||||
@ -212,7 +213,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro
|
|||||||
storage.setActive(groupID, false)
|
storage.setActive(groupID, false)
|
||||||
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success {
|
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success {
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = SignalServiceProtos.GroupContext.Type.QUIT
|
val infoType = SignalServiceGroup.Type.QUIT
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
if (notifyUser) {
|
if (notifyUser) {
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.session.libsession.messaging.sending_receiving.dataextraction
|
||||||
|
|
||||||
|
class DataExtractionNotificationInfoMessage {
|
||||||
|
|
||||||
|
enum class Kind {
|
||||||
|
SCREENSHOT,
|
||||||
|
MEDIA_SAVED
|
||||||
|
}
|
||||||
|
|
||||||
|
var kind: Kind? = null
|
||||||
|
|
||||||
|
constructor(kind: Kind?) {
|
||||||
|
this.kind = kind
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package org.session.libsession.messaging.utilities
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.session.libsession.R
|
||||||
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
|
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||||
|
import org.session.libsession.utilities.ExpirationUtil
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||||
|
|
||||||
|
object UpdateMessageBuilder {
|
||||||
|
|
||||||
|
fun buildGroupUpdateMessage(context: Context, updateMessageData: UpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String {
|
||||||
|
var message = ""
|
||||||
|
val updateData = updateMessageData.kind ?: return message
|
||||||
|
if (!isOutgoing && sender == null) return message
|
||||||
|
val senderName: String = if (!isOutgoing) {
|
||||||
|
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||||
|
} else { context.getString(R.string.MessageRecord_you) }
|
||||||
|
|
||||||
|
when (updateData) {
|
||||||
|
is UpdateMessageData.Kind.GroupCreation -> {
|
||||||
|
message = if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_you_created_a_new_group)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UpdateMessageData.Kind.GroupNameChange -> {
|
||||||
|
message = if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UpdateMessageData.Kind.GroupMemberAdded -> {
|
||||||
|
val members = updateData.updatedMembers.joinToString(", ") {
|
||||||
|
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it
|
||||||
|
}
|
||||||
|
message = if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_you_added_s_to_the_group, members)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UpdateMessageData.Kind.GroupMemberRemoved -> {
|
||||||
|
val storage = MessagingConfiguration.shared.storage
|
||||||
|
val userPublicKey = storage.getUserPublicKey()!!
|
||||||
|
// 1st case: you are part of the removed members
|
||||||
|
message = if (userPublicKey in updateData.updatedMembers) {
|
||||||
|
if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_left_group)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.MessageRecord_you_were_removed_from_the_group)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 2nd case: you are not part of the removed members
|
||||||
|
val members = updateData.updatedMembers.joinToString(", ") {
|
||||||
|
storage.getDisplayNameForRecipient(it) ?: it
|
||||||
|
}
|
||||||
|
if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.MessageRecord_s_removed_s_from_the_group, senderName, members)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UpdateMessageData.Kind.GroupMemberLeft -> {
|
||||||
|
message = if (isOutgoing) {
|
||||||
|
context.getString(R.string.MessageRecord_left_group)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.ConversationItem_group_action_left, senderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildExpirationTimerMessage(context: Context, duration: Long, sender: String? = null, isOutgoing: Boolean = false): String {
|
||||||
|
if (!isOutgoing && sender == null) return ""
|
||||||
|
val senderName: String? = if (!isOutgoing) {
|
||||||
|
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||||
|
} else { context.getString(R.string.MessageRecord_you) }
|
||||||
|
return if (duration <= 0) {
|
||||||
|
if (isOutgoing) context.getString(R.string.MessageRecord_you_disabled_disappearing_messages)
|
||||||
|
else context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, senderName)
|
||||||
|
} else {
|
||||||
|
val time = ExpirationUtil.getExpirationDisplayValue(context, duration.toInt())
|
||||||
|
if (isOutgoing)context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time)
|
||||||
|
else context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, senderName, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, sender: String? = null): String {
|
||||||
|
val senderName = MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||||
|
return when (kind) {
|
||||||
|
DataExtractionNotificationInfoMessage.Kind.SCREENSHOT ->
|
||||||
|
context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName)
|
||||||
|
DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED ->
|
||||||
|
context.getString(R.string.MessageRecord_media_saved_by_s, senderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package org.session.libsession.messaging.utilities
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||||
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
// class used to save update messages details
|
||||||
|
class UpdateMessageData () {
|
||||||
|
|
||||||
|
var kind: Kind? = null
|
||||||
|
|
||||||
|
//the annotations below are required for serialization. Any new Kind class MUST be declared as JsonSubTypes as well
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
|
||||||
|
@JsonSubTypes(
|
||||||
|
JsonSubTypes.Type(Kind.GroupCreation::class, name = "GroupCreation"),
|
||||||
|
JsonSubTypes.Type(Kind.GroupNameChange::class, name = "GroupNameChange"),
|
||||||
|
JsonSubTypes.Type(Kind.GroupMemberAdded::class, name = "GroupMemberAdded"),
|
||||||
|
JsonSubTypes.Type(Kind.GroupMemberRemoved::class, name = "GroupMemberRemoved"),
|
||||||
|
JsonSubTypes.Type(Kind.GroupMemberLeft::class, name = "GroupMemberLeft")
|
||||||
|
)
|
||||||
|
sealed class Kind() {
|
||||||
|
class GroupCreation(): Kind()
|
||||||
|
class GroupNameChange(val name: String): Kind() {
|
||||||
|
constructor(): this("") //default constructor required for json serialization
|
||||||
|
}
|
||||||
|
class GroupMemberAdded(val updatedMembers: Collection<String>): Kind() {
|
||||||
|
constructor(): this(Collections.emptyList())
|
||||||
|
}
|
||||||
|
class GroupMemberRemoved(val updatedMembers: Collection<String>): Kind() {
|
||||||
|
constructor(): this(Collections.emptyList())
|
||||||
|
}
|
||||||
|
class GroupMemberLeft(): Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(kind: Kind): this() {
|
||||||
|
this.kind = kind
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG = UpdateMessageData::class.simpleName
|
||||||
|
|
||||||
|
fun buildGroupUpdate(type: SignalServiceGroup.Type, name: String, members: Collection<String>): UpdateMessageData? {
|
||||||
|
return when(type) {
|
||||||
|
SignalServiceGroup.Type.CREATION -> UpdateMessageData(Kind.GroupCreation())
|
||||||
|
SignalServiceGroup.Type.NAME_CHANGE -> UpdateMessageData(Kind.GroupNameChange(name))
|
||||||
|
SignalServiceGroup.Type.MEMBER_ADDED -> UpdateMessageData(Kind.GroupMemberAdded(members))
|
||||||
|
SignalServiceGroup.Type.MEMBER_REMOVED -> UpdateMessageData(Kind.GroupMemberRemoved(members))
|
||||||
|
SignalServiceGroup.Type.QUIT -> UpdateMessageData(Kind.GroupMemberLeft())
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromJSON(json: String): UpdateMessageData? {
|
||||||
|
return try {
|
||||||
|
JsonUtil.fromJson(json, UpdateMessageData::class.java)
|
||||||
|
} catch (e: JsonParseException) {
|
||||||
|
Log.e(TAG, "${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJSON(): String {
|
||||||
|
return JsonUtil.toJson(this)
|
||||||
|
}
|
||||||
|
}
|
@ -487,6 +487,16 @@
|
|||||||
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message.</string>
|
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message.</string>
|
||||||
<string name="MessageRecord_left_group">You have left the group.</string>
|
<string name="MessageRecord_left_group">You have left the group.</string>
|
||||||
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
||||||
|
<string name="MessageRecord_you_created_a_new_group">You created a new group.</string>
|
||||||
|
<string name="MessageRecord_s_added_you_to_the_group">%1$s added you to the group.</string>
|
||||||
|
<string name="MessageRecord_you_renamed_the_group_to_s">You renamed the group to %1$s</string>
|
||||||
|
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s renamed the group to: %2$s</string>
|
||||||
|
<string name="MessageRecord_you_added_s_to_the_group">You added %1$s to the group.</string>
|
||||||
|
<string name="MessageRecord_s_added_s_to_the_group">%1$s added %2$s to the group.</string>
|
||||||
|
<string name="MessageRecord_you_removed_s_from_the_group">You removed %1$s from the group.</string>
|
||||||
|
<string name="MessageRecord_s_removed_s_from_the_group">%1$s removed %2$s from the group.</string>
|
||||||
|
<string name="MessageRecord_you_were_removed_from_the_group">You were removed from the group.</string>
|
||||||
|
<string name="MessageRecord_you">You</string>
|
||||||
<string name="MessageRecord_you_called">You called</string>
|
<string name="MessageRecord_you_called">You called</string>
|
||||||
<string name="MessageRecord_called_you">Contact called</string>
|
<string name="MessageRecord_called_you">Contact called</string>
|
||||||
<string name="MessageRecord_missed_call">Missed call</string>
|
<string name="MessageRecord_missed_call">Missed call</string>
|
||||||
@ -499,6 +509,8 @@
|
|||||||
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
|
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
|
||||||
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s</string>
|
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s</string>
|
||||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s</string>
|
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s</string>
|
||||||
|
<string name="MessageRecord_s_took_a_screenshot">%1$s took a screenshot.</string>
|
||||||
|
<string name="MessageRecord_media_saved_by_s">Media saved by %1$s.</string>
|
||||||
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
||||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
||||||
|
@ -37,7 +37,11 @@ public class SignalServiceGroup {
|
|||||||
UPDATE,
|
UPDATE,
|
||||||
DELIVER,
|
DELIVER,
|
||||||
QUIT,
|
QUIT,
|
||||||
REQUEST_INFO
|
REQUEST_INFO,
|
||||||
|
CREATION,
|
||||||
|
NAME_CHANGE,
|
||||||
|
MEMBER_ADDED,
|
||||||
|
MEMBER_REMOVED
|
||||||
}
|
}
|
||||||
|
|
||||||
private final byte[] groupId;
|
private final byte[] groupId;
|
||||||
|
Loading…
Reference in New Issue
Block a user