Merge remote-tracking branch 'upstream/dev' into libsession-integration

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
#	app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt
#	libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
This commit is contained in:
0x330a 2023-03-31 13:41:41 +11:00
commit d5e4e6b7cd
32 changed files with 328 additions and 119 deletions

View File

@ -43,6 +43,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
implementation 'androidx.activity:activity-ktx:1.5.1' implementation 'androidx.activity:activity-ktx:1.5.1'
implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation 'androidx.fragment:fragment-ktx:1.5.3'

View File

@ -287,7 +287,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
if (poller != null) { if (poller != null) {
poller.stopIfNeeded(); poller.stopIfNeeded();
} }
ClosedGroupPollerV2.getShared().stop(); ClosedGroupPollerV2.getShared().stopAll();
} }
@Override @Override
@ -467,11 +467,15 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
String token = task.getResult().getToken(); String token = task.getResult().getToken();
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return Unit.INSTANCE; if (userPublicKey == null) return Unit.INSTANCE;
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
if (TextSecurePreferences.isUsingFCM(this)) { if (TextSecurePreferences.isUsingFCM(this)) {
LokiPushNotificationManager.register(token, userPublicKey, this, force); LokiPushNotificationManager.register(token, userPublicKey, this, force);
} else { } else {
LokiPushNotificationManager.unregister(token, this); LokiPushNotificationManager.unregister(token, this);
} }
});
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
} }

View File

@ -54,6 +54,7 @@ import com.google.android.material.tabs.TabLayout;
import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.messages.control.DataExtractionNotification;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.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;
@ -366,7 +367,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
private void sendMediaSavedNotificationIfNeeded() { private void sendMediaSavedNotificationIfNeeded() {
if (recipient.isGroupRecipient()) return; if (recipient.isGroupRecipient()) return;
DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset()));
MessageSender.send(message, recipient.getAddress()); MessageSender.send(message, recipient.getAddress());
} }

View File

@ -60,6 +60,7 @@ import androidx.viewpager.widget.ViewPager;
import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.messages.control.DataExtractionNotification;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient;
@ -423,7 +424,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) .onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
.onAllGranted(() -> { .onAllGranted(() -> {
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis(); long saveDate = (mediaItem.date > 0) ? mediaItem.date : SnodeAPI.getNowWithOffset();
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));
@ -437,7 +438,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
private void sendMediaSavedNotificationIfNeeded() { private void sendMediaSavedNotificationIfNeeded() {
if (conversationRecipient.isGroupRecipient()) return; if (conversationRecipient.isGroupRecipient()) return;
DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset()));
MessageSender.send(message, conversationRecipient.getAddress()); MessageSender.send(message, conversationRecipient.getAddress());
} }

View File

@ -13,6 +13,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.snode.SnodeAPI;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView; import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -106,7 +107,7 @@ public class ConversationItemFooter extends LinearLayout {
messageRecord.getExpiresIn()); messageRecord.getExpiresIn());
this.timerView.startAnimation(); this.timerView.startAnimation();
if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) { if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= SnodeAPI.getNowWithOffset()) {
ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule(); ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule();
} }
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) { } else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {

View File

@ -166,6 +166,7 @@ import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.SaveAttachmentTask import org.thoughtcrime.securesms.util.SaveAttachmentTask
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.util.toPx
import java.lang.ref.WeakReference
import java.util.Locale import java.util.Locale
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -364,12 +365,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// messageIdToScroll // messageIdToScroll
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1)) messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
val thread = threadDb.getRecipientForThreadId(viewModel.threadId) val recipient = viewModel.recipient
if (thread == null) { val openGroup = recipient.let { viewModel.openGroup }
if (recipient == null || (recipient.isOpenGroupRecipient && openGroup == null)) {
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show() Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
return finish() return finish()
} }
setUpRecyclerView()
setUpToolBar() setUpToolBar()
setUpInputBar() setUpInputBar()
setUpLinkPreviewObserver() setUpLinkPreviewObserver()
@ -396,22 +398,31 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
} }
} }
unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId)
updateUnreadCountIndicator() updateUnreadCountIndicator()
setUpTypingObserver()
setUpRecipientObserver()
updateSubtitle() updateSubtitle()
getLatestOpenGroupInfoIfNeeded()
setUpBlockedBanner() setUpBlockedBanner()
binding!!.searchBottomBar.setEventListener(this) binding!!.searchBottomBar.setEventListener(this)
setUpSearchResultObserver()
scrollToFirstUnreadMessageIfNeeded()
showOrHideInputIfNeeded() showOrHideInputIfNeeded()
setUpMessageRequestsBar() setUpMessageRequestsBar()
viewModel.recipient?.let { recipient ->
if (recipient.isOpenGroupRecipient && viewModel.openGroup == null) { val weakActivity = WeakReference(this)
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
return finish() lifecycleScope.launch(Dispatchers.IO) {
unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId)
// Note: We are accessing the `adapter` property because we want it to be loaded on
// the background thread to avoid blocking the UI thread and potentially hanging when
// transitioning to the activity
weakActivity.get()?.adapter ?: return@launch
withContext(Dispatchers.Main) {
setUpRecyclerView()
setUpTypingObserver()
setUpRecipientObserver()
getLatestOpenGroupInfoIfNeeded()
setUpSearchResultObserver()
scrollToFirstUnreadMessageIfNeeded()
} }
} }
@ -709,6 +720,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// region Animation & Updating // region Animation & Updating
override fun onModified(recipient: Recipient) { override fun onModified(recipient: Recipient) {
viewModel.updateRecipient()
runOnUiThread { runOnUiThread {
val threadRecipient = viewModel.recipient ?: return@runOnUiThread Log.d("Loki-DBG", "Recipient no longer here, go back?") val threadRecipient = viewModel.recipient ?: return@runOnUiThread Log.d("Loki-DBG", "Recipient no longer here, go back?")
if (threadRecipient.isContactRecipient) { if (threadRecipient.isContactRecipient) {
@ -1063,7 +1076,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
storage.setExpirationTimer(thread.address.serialize(), expirationTime) storage.setExpirationTimer(thread.address.serialize(), expirationTime)
val message = ExpirationTimerUpdate(expirationTime) val message = ExpirationTimerUpdate(expirationTime)
message.recipient = thread.address.serialize() message.recipient = thread.address.serialize()
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = SnodeAPI.nowWithOffset
val expiringMessageManager = ApplicationContext.getInstance(this).expiringMessageManager val expiringMessageManager = ApplicationContext.getInstance(this).expiringMessageManager
expiringMessageManager.setExpirationTimer(message) expiringMessageManager.setExpirationTimer(message)
MessageSender.send(message, thread.address) MessageSender.send(message, thread.address)
@ -1192,7 +1205,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// Create the message // Create the message
val recipient = viewModel.recipient ?: return val recipient = viewModel.recipient ?: return
val reactionMessage = VisibleMessage() val reactionMessage = VisibleMessage()
val emojiTimestamp = System.currentTimeMillis() val emojiTimestamp = SnodeAPI.nowWithOffset
reactionMessage.sentTimestamp = emojiTimestamp reactionMessage.sentTimestamp = emojiTimestamp
val author = textSecurePreferences.getLocalNumber()!! val author = textSecurePreferences.getLocalNumber()!!
// Put the message in the database // Put the message in the database
@ -1225,7 +1238,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun sendEmojiRemoval(emoji: String, originalMessage: MessageRecord) { private fun sendEmojiRemoval(emoji: String, originalMessage: MessageRecord) {
val recipient = viewModel.recipient ?: return val recipient = viewModel.recipient ?: return
val message = VisibleMessage() val message = VisibleMessage()
val emojiTimestamp = System.currentTimeMillis() val emojiTimestamp = SnodeAPI.nowWithOffset
message.sentTimestamp = emojiTimestamp message.sentTimestamp = emojiTimestamp
val author = textSecurePreferences.getLocalNumber()!! val author = textSecurePreferences.getLocalNumber()!!
reactionDb.deleteReaction(emoji, MessageId(originalMessage.id, originalMessage.isMms), author, false) reactionDb.deleteReaction(emoji, MessageId(originalMessage.id, originalMessage.isMms), author, false)
@ -1454,7 +1467,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
// Create the message // Create the message
val message = VisibleMessage() val message = VisibleMessage()
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = SnodeAPI.nowWithOffset
message.text = text message.text = text
val outgoingTextMessage = OutgoingTextMessage.from(message, recipient) val outgoingTextMessage = OutgoingTextMessage.from(message, recipient)
// Clear the input bar // Clear the input bar
@ -1478,7 +1491,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
processMessageRequestApproval() processMessageRequestApproval()
// Create the message // Create the message
val message = VisibleMessage() val message = VisibleMessage()
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = SnodeAPI.nowWithOffset
message.text = body message.text = body
val quote = quotedMessage?.let { val quote = quotedMessage?.let {
val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf() val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf()
@ -1875,7 +1888,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun sendMediaSavedNotification() { private fun sendMediaSavedNotification() {
val recipient = viewModel.recipient ?: return val recipient = viewModel.recipient ?: return
if (recipient.isGroupRecipient) { return } if (recipient.isGroupRecipient) { return }
val timestamp = System.currentTimeMillis() val timestamp = SnodeAPI.nowWithOffset
val kind = DataExtractionNotification.Kind.MediaSaved(timestamp) val kind = DataExtractionNotification.Kind.MediaSaved(timestamp)
val message = DataExtractionNotification(kind) val message = DataExtractionNotification(kind)
MessageSender.send(message, recipient.address) MessageSender.send(message, recipient.address)

View File

@ -38,11 +38,17 @@ class ConversationViewModel(
private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = recipient != null)) private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = recipient != null))
val uiState: StateFlow<ConversationUiState> = _uiState val uiState: StateFlow<ConversationUiState> = _uiState
private var _recipient: RetrieveOnce<Recipient> = RetrieveOnce {
repository.maybeGetRecipientForThreadId(threadId)
}
val recipient: Recipient? val recipient: Recipient?
get() = repository.maybeGetRecipientForThreadId(threadId) get() = _recipient.value
private var _openGroup: RetrieveOnce<OpenGroup> = RetrieveOnce {
storage.getOpenGroup(threadId)
}
val openGroup: OpenGroup? val openGroup: OpenGroup?
get() = storage.getOpenGroup(threadId) get() = _openGroup.value
val serverCapabilities: List<String> val serverCapabilities: List<String>
get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf() get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf()
@ -186,6 +192,10 @@ class ConversationViewModel(
return repository.hasReceived(threadId) return repository.hasReceived(threadId)
} }
fun updateRecipient() {
_recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId))
}
@dagger.assisted.AssistedFactory @dagger.assisted.AssistedFactory
interface AssistedFactory { interface AssistedFactory {
fun create(threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory fun create(threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory
@ -213,3 +223,19 @@ data class ConversationUiState(
val isMessageRequestAccepted: Boolean? = null, val isMessageRequestAccepted: Boolean? = null,
val conversationExists: Boolean val conversationExists: Boolean
) )
data class RetrieveOnce<T>(val retrieval: () -> T?) {
private var triedToRetrieve: Boolean = false
private var _value: T? = null
val value: T?
get() {
if (triedToRetrieve) { return _value }
triedToRetrieve = true
_value = retrieval()
return _value
}
fun updateTo(value: T?) { _value = value }
}

View File

@ -10,6 +10,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -88,7 +89,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
binding.expiresContainer.visibility = View.GONE binding.expiresContainer.visibility = View.GONE
} else { } else {
binding.expiresContainer.visibility = View.VISIBLE binding.expiresContainer.visibility = View.VISIBLE
val elapsed = System.currentTimeMillis() - messageRecord!!.expireStarted val elapsed = SnodeAPI.nowWithOffset - messageRecord!!.expireStarted
val remaining = messageRecord!!.expiresIn - elapsed val remaining = messageRecord!!.expiresIn - elapsed
val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1)) val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1))

View File

@ -26,6 +26,7 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
@ -315,7 +316,7 @@ class VisibleMessageView : LinearLayout {
if (message.expireStarted > 0) { if (message.expireStarted > 0) {
binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
binding.expirationTimerView.startAnimation() binding.expirationTimerView.startAnimation()
if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) { if (message.expireStarted + message.expiresIn <= SnodeAPI.nowWithOffset) {
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule() ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
} }
} else if (!message.isMediaPending) { } else if (!message.isMediaPending) {

View File

@ -33,6 +33,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
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.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.UNKNOWN import org.session.libsession.utilities.Address.Companion.UNKNOWN
import org.session.libsession.utilities.Address.Companion.fromExternal import org.session.libsession.utilities.Address.Companion.fromExternal
@ -340,7 +341,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
} }
override fun markExpireStarted(messageId: Long) { override fun markExpireStarted(messageId: Long) {
markExpireStarted(messageId, System.currentTimeMillis()) markExpireStarted(messageId, SnodeAPI.nowWithOffset)
} }
override fun markExpireStarted(messageId: Long, startedTimestamp: Long) { override fun markExpireStarted(messageId: Long, startedTimestamp: Long) {
@ -731,7 +732,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
// In open groups messages should be sorted by their server timestamp // In open groups messages should be sorted by their server timestamp
var receivedTimestamp = serverTimestamp var receivedTimestamp = serverTimestamp
if (serverTimestamp == 0L) { if (serverTimestamp == 0L) {
receivedTimestamp = System.currentTimeMillis() receivedTimestamp = SnodeAPI.nowWithOffset
} }
contentValues.put(DATE_RECEIVED, receivedTimestamp) contentValues.put(DATE_RECEIVED, receivedTimestamp)
contentValues.put(SUBSCRIPTION_ID, message.subscriptionId) contentValues.put(SUBSCRIPTION_ID, message.subscriptionId)
@ -1210,7 +1211,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val slideDeck = SlideDeck(context, message!!.attachments) val slideDeck = SlideDeck(context, message!!.attachments)
return MediaMmsMessageRecord( return MediaMmsMessageRecord(
id, message.recipient, message.recipient, id, message.recipient, message.recipient,
1, System.currentTimeMillis(), System.currentTimeMillis(), 1, SnodeAPI.nowWithOffset, SnodeAPI.nowWithOffset,
0, threadId, message.body, 0, threadId, message.body,
slideDeck, slideDeck.slides.size, slideDeck, slideDeck.slides.size,
if (message.isSecure) MmsSmsColumns.Types.getOutgoingEncryptedMessageType() else MmsSmsColumns.Types.getOutgoingSmsMessageType(), if (message.isSecure) MmsSmsColumns.Types.getOutgoingEncryptedMessageType() else MmsSmsColumns.Types.getOutgoingSmsMessageType(),
@ -1218,7 +1219,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
LinkedList(), LinkedList(),
message.subscriptionId, message.subscriptionId,
message.expiresIn, message.expiresIn,
System.currentTimeMillis(), 0, SnodeAPI.nowWithOffset, 0,
if (message.outgoingQuote != null) Quote( if (message.outgoingQuote != null) Quote(
message.outgoingQuote!!.id, message.outgoingQuote!!.id,
message.outgoingQuote!!.author, message.outgoingQuote!!.author,

View File

@ -36,6 +36,7 @@ import org.session.libsession.messaging.calls.CallMessageType;
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
import org.session.libsession.messaging.messages.signal.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.IdentityKeyMismatch;
import org.session.libsession.utilities.IdentityKeyMismatchList; import org.session.libsession.utilities.IdentityKeyMismatchList;
@ -223,7 +224,7 @@ public class SmsDatabase extends MessagingDatabase {
@Override @Override
public void markExpireStarted(long id) { public void markExpireStarted(long id) {
markExpireStarted(id, System.currentTimeMillis()); markExpireStarted(id, SnodeAPI.getNowWithOffset());
} }
@Override @Override
@ -525,7 +526,7 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(ADDRESS, address.serialize()); contentValues.put(ADDRESS, address.serialize());
contentValues.put(THREAD_ID, threadId); contentValues.put(THREAD_ID, threadId);
contentValues.put(BODY, message.getMessageBody()); contentValues.put(BODY, message.getMessageBody());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); contentValues.put(DATE_RECEIVED, SnodeAPI.getNowWithOffset());
contentValues.put(DATE_SENT, message.getSentTimestampMillis()); contentValues.put(DATE_SENT, message.getSentTimestampMillis());
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(TYPE, type); contentValues.put(TYPE, type);
@ -760,11 +761,11 @@ public class SmsDatabase extends MessagingDatabase {
public MessageRecord getCurrent() { public MessageRecord getCurrent() {
return new SmsMessageRecord(id, message.getMessageBody(), return new SmsMessageRecord(id, message.getMessageBody(),
message.getRecipient(), message.getRecipient(), message.getRecipient(), message.getRecipient(),
System.currentTimeMillis(), System.currentTimeMillis(), SnodeAPI.getNowWithOffset(), SnodeAPI.getNowWithOffset(),
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList<IdentityKeyMismatch>(), threadId, 0, new LinkedList<IdentityKeyMismatch>(),
message.getExpiresIn(), message.getExpiresIn(),
System.currentTimeMillis(), 0, false, Collections.emptyList(), false); SnodeAPI.getNowWithOffset(), 0, false, Collections.emptyList(), false);
} }
} }

View File

@ -156,7 +156,7 @@ public class ThreadDatabase extends Database {
private long createThreadForRecipient(Address address, boolean group, int distributionType) { private long createThreadForRecipient(Address address, boolean group, int distributionType) {
ContentValues contentValues = new ContentValues(4); ContentValues contentValues = new ContentValues(4);
long date = System.currentTimeMillis(); long date = SnodeAPI.getNowWithOffset();
contentValues.put(DATE, date - date % 1000); contentValues.put(DATE, date - date % 1000);
contentValues.put(ADDRESS, address.serialize()); contentValues.put(ADDRESS, address.serialize());
@ -335,7 +335,7 @@ public class ThreadDatabase extends Database {
contentValues.put(UNREAD_MENTION_COUNT, 0); contentValues.put(UNREAD_MENTION_COUNT, 0);
if (lastSeen) { if (lastSeen) {
contentValues.put(LAST_SEEN, System.currentTimeMillis()); contentValues.put(LAST_SEEN, SnodeAPI.getNowWithOffset());
} }
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();

View File

@ -30,6 +30,7 @@ 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.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Log;
@ -82,7 +83,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
VisibleMessage message = new VisibleMessage(); VisibleMessage message = new VisibleMessage();
message.setText(responseText.toString()); message.setText(responseText.toString());
message.setSentTimestamp(System.currentTimeMillis()); message.setSentTimestamp(SnodeAPI.getNowWithOffset());
MessageSender.send(message, recipient.getAddress()); MessageSender.send(message, recipient.getAddress());
if (recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
@ -96,7 +97,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
} else { } else {
Log.w("AndroidAutoReplyReceiver", "Sending regular message "); Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient); OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient);
DatabaseComponent.get(context).smsDatabase().insertMessageOutbox(replyThreadId, reply, false, System.currentTimeMillis(), null, true); DatabaseComponent.get(context).smsDatabase().insertMessageOutbox(replyThreadId, reply, false, SnodeAPI.getNowWithOffset(), null, true);
} }
List<MarkedMessageInfo> messageIds = DatabaseComponent.get(context).threadDatabase().setRead(replyThreadId, true); List<MarkedMessageInfo> messageIds = DatabaseComponent.get(context).threadDatabase().setRead(replyThreadId, true);

View File

@ -4,9 +4,11 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
@ -21,19 +23,35 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.recover
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
enum class Targets {
DMS, CLOSED_GROUPS, OPEN_GROUPS
}
companion object { companion object {
const val TAG = "BackgroundPollWorker" const val TAG = "BackgroundPollWorker"
const val INITIAL_SCHEDULE_TIME = "INITIAL_SCHEDULE_TIME"
const val REQUEST_TARGETS = "REQUEST_TARGETS"
@JvmStatic @JvmStatic
fun schedulePeriodic(context: Context) { fun schedulePeriodic(context: Context) = schedulePeriodic(context, targets = Targets.values())
@JvmStatic
fun schedulePeriodic(context: Context, targets: Array<Targets>) {
Log.v(TAG, "Scheduling periodic work.") Log.v(TAG, "Scheduling periodic work.")
val builder = PeriodicWorkRequestBuilder<BackgroundPollWorker>(15, TimeUnit.MINUTES) val durationMinutes: Long = 15
val builder = PeriodicWorkRequestBuilder<BackgroundPollWorker>(durationMinutes, TimeUnit.MINUTES)
builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
val dataBuilder = Data.Builder()
dataBuilder.putLong(INITIAL_SCHEDULE_TIME, System.currentTimeMillis() + (durationMinutes * 60 * 1000))
dataBuilder.putStringArray(REQUEST_TARGETS, targets.map { it.name }.toTypedArray())
builder.setInputData(dataBuilder.build())
val workRequest = builder.build() val workRequest = builder.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork( WorkManager.getInstance(context).enqueueUniquePeriodicWork(
TAG, TAG,
@ -41,6 +59,20 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
workRequest workRequest
) )
} }
@JvmStatic
fun scheduleOnce(context: Context, targets: Array<Targets> = Targets.values()) {
Log.v(TAG, "Scheduling single run.")
val builder = OneTimeWorkRequestBuilder<BackgroundPollWorker>()
builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
val dataBuilder = Data.Builder()
dataBuilder.putStringArray(REQUEST_TARGETS, targets.map { it.name }.toTypedArray())
builder.setInputData(dataBuilder.build())
val workRequest = builder.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
} }
override fun doWork(): Result { override fun doWork(): Result {
@ -49,13 +81,35 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
return Result.failure() return Result.failure()
} }
// If this is a scheduled run and it is happening before the initial scheduled time (as
// periodic background tasks run immediately when scheduled) then don't actually do anything
// because this might slow requests on initial startup or triggered by PNs
val initialScheduleTime = inputData.getLong(INITIAL_SCHEDULE_TIME, -1)
if (initialScheduleTime != -1L && System.currentTimeMillis() < (initialScheduleTime - (60 * 1000))) {
Log.v(TAG, "Skipping initial run.")
return Result.success()
}
// Retrieve the desired targets (defaulting to all if not provided or empty)
val requestTargets: List<Targets> = (inputData.getStringArray(REQUEST_TARGETS) ?: emptyArray())
.map {
try { Targets.valueOf(it) }
catch(e: Exception) { null }
}
.filterNotNull()
.ifEmpty { Targets.values().toList() }
try { try {
Log.v(TAG, "Performing background poll.") Log.v(TAG, "Performing background poll for ${requestTargets.joinToString { it.name }}.")
val promises = mutableListOf<Promise<Unit, Exception>>() val promises = mutableListOf<Promise<Unit, Exception>>()
// DMs // DMs
var dmsPromise: Promise<Unit, Exception> = Promise.ofSuccess(Unit)
if (requestTargets.contains(Targets.DMS)) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val dmsPromise = SnodeAPI.getMessages(userPublicKey).bind { envelopes -> dmsPromise = SnodeAPI.getMessages(userPublicKey).bind { envelopes ->
val params = envelopes.map { (envelope, serverHash) -> val params = envelopes.map { (envelope, serverHash) ->
// FIXME: Using a job here seems like a bad idea... // FIXME: Using a job here seems like a bad idea...
MessageReceiveParameters(envelope.toByteArray(), serverHash, null) MessageReceiveParameters(envelope.toByteArray(), serverHash, null)
@ -63,14 +117,20 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
BatchMessageReceiveJob(params).executeAsync("background") BatchMessageReceiveJob(params).executeAsync("background")
} }
promises.add(dmsPromise) promises.add(dmsPromise)
}
// Closed groups // Closed groups
if (requestTargets.contains(Targets.CLOSED_GROUPS)) {
val closedGroupPoller = ClosedGroupPollerV2() // Intentionally don't use shared val closedGroupPoller = ClosedGroupPollerV2() // Intentionally don't use shared
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys() val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
allGroupPublicKeys.iterator().forEach { closedGroupPoller.poll(it) } allGroupPublicKeys.iterator().forEach { closedGroupPoller.poll(it) }
}
// Open Groups // Open Groups
var ogPollError: Exception? = null
if (requestTargets.contains(Targets.OPEN_GROUPS)) {
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
val openGroups = threadDB.getAllOpenGroups() val openGroups = threadDB.getAllOpenGroups()
val openGroupServers = openGroups.map { it.value.server }.toSet() val openGroupServers = openGroups.map { it.value.server }.toSet()
@ -78,12 +138,32 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
for (server in openGroupServers) { for (server in openGroupServers) {
val poller = OpenGroupPoller(server, null) val poller = OpenGroupPoller(server, null)
poller.hasStarted = true poller.hasStarted = true
promises.add(poller.poll())
// If one of the open group pollers fails we don't want it to cancel the DM
// poller so just hold on to the error for later
promises.add(
poller.poll().recover {
if (dmsPromise.isDone()) {
throw it
}
ogPollError = it
}
)
}
} }
// Wait until all the promises are resolved // Wait until all the promises are resolved
all(promises).get() all(promises).get()
// If the Open Group pollers threw an exception then re-throw it here (now that
// the DM promise has completed)
val localOgPollException = ogPollError
if (localOgPollException != null) {
throw localOgPollException
}
return Result.success() return Result.success()
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Background poll failed due to error: ${exception.message}.", exception) Log.e(TAG, "Background poll failed due to error: ${exception.message}.", exception)

View File

@ -44,6 +44,7 @@ import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.utilities.SessionId; import org.session.libsession.messaging.utilities.SessionId;
import org.session.libsession.messaging.utilities.SodiumUtilities; import org.session.libsession.messaging.utilities.SodiumUtilities;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.Contact;
import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.ServiceUtil;
@ -137,7 +138,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
Intent intent = new Intent(context, ConversationActivityV2.class); Intent intent = new Intent(context, ConversationActivityV2.class);
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
intent.setData((Uri.parse("custom://" + System.currentTimeMillis()))); intent.setData((Uri.parse("custom://" + SnodeAPI.getNowWithOffset())));
FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent); FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)) ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))

View File

@ -82,7 +82,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
if (!SessionMetaProtocol.shouldSendReadReceipt(Recipient.from(context, address, false))) { continue; } if (!SessionMetaProtocol.shouldSendReadReceipt(Recipient.from(context, address, false))) { continue; }
ReadReceipt readReceipt = new ReadReceipt(timestamps); ReadReceipt readReceipt = new ReadReceipt(timestamps);
readReceipt.setSentTimestamp(System.currentTimeMillis()); readReceipt.setSentTimestamp(SnodeAPI.getNowWithOffset());
MessageSender.send(readReceipt, address); MessageSender.send(readReceipt, address);
} }
} }

View File

@ -115,7 +115,7 @@ class DefaultConversationRepository @Inject constructor(
val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return
for (contact in contacts) { for (contact in contacts) {
val message = VisibleMessage() val message = VisibleMessage()
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = SnodeAPI.nowWithOffset
val openGroupInvitation = OpenGroupInvitation() val openGroupInvitation = OpenGroupInvitation()
openGroupInvitation.name = openGroup.name openGroupInvitation.name = openGroup.name
openGroupInvitation.url = openGroup.joinURL openGroupInvitation.url = openGroup.joinURL

View File

@ -9,6 +9,7 @@ import android.widget.Toast;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Log;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
@ -50,7 +51,7 @@ public class QuickResponseService extends IntentService {
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
VisibleMessage message = new VisibleMessage(); VisibleMessage message = new VisibleMessage();
message.setText(content); message.setText(content);
message.setSentTimestamp(System.currentTimeMillis()); message.setSentTimestamp(SnodeAPI.getNowWithOffset());
MessageSender.send(message, Address.fromExternal(this, number)); MessageSender.send(message, Address.fromExternal(this, number));
} }
} catch (URISyntaxException e) { } catch (URISyntaxException e) {

View File

@ -1,6 +1,6 @@
package org.thoughtcrime.securesms.service package org.thoughtcrime.securesms.service
import android.app.Service import android.app.ForegroundServiceStartNotAllowedException
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -17,6 +17,8 @@ import android.telephony.PhoneStateListener.LISTEN_NONE
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType
@ -25,6 +27,7 @@ import org.session.libsession.utilities.FutureTaskListener
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.calls.WebRtcCallActivity import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker
import org.thoughtcrime.securesms.util.CallNotificationBuilder import org.thoughtcrime.securesms.util.CallNotificationBuilder
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING
@ -46,7 +49,7 @@ import javax.inject.Inject
import org.thoughtcrime.securesms.webrtc.data.State as CallState import org.thoughtcrime.securesms.webrtc.data.State as CallState
@AndroidEntryPoint @AndroidEntryPoint
class WebRtcCallService : Service(), CallManager.WebRtcListener { class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
companion object { companion object {
@ -238,8 +241,11 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
scheduledReconnect?.cancel(false) scheduledReconnect?.cancel(false)
scheduledTimeout = null scheduledTimeout = null
scheduledReconnect = null scheduledReconnect = null
lifecycleScope.launchWhenCreated {
stopForeground(true) stopForeground(true)
} }
}
private fun isSameCall(intent: Intent): Boolean { private fun isSameCall(intent: Intent): Boolean {
val expectedCallId = getCallId(intent) val expectedCallId = getCallId(intent)
@ -253,7 +259,9 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
private fun isIdle() = callManager.isIdle() private fun isIdle() = callManager.isIdle()
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent): IBinder? {
return super.onBind(intent)
}
override fun onHangup() { override fun onHangup() {
serviceExecutor.execute { serviceExecutor.execute {
@ -272,7 +280,8 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
if (intent == null || intent.action == null) return START_NOT_STICKY if (intent == null || intent.action == null) return START_NOT_STICKY
serviceExecutor.execute { serviceExecutor.execute {
val action = intent.action val action = intent.action
Log.i("Loki", "Handling ${intent.action}") val callId = ((intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID)?.toString() ?: "No callId")
Log.i("Loki", "Handling ${intent.action} for call: ${callId}")
when { when {
action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer( action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(
intent intent
@ -361,9 +370,11 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
insertMissedCall(recipient, false) insertMissedCall(recipient, false)
if (callState == CallState.Idle) { if (callState == CallState.Idle) {
lifecycleScope.launchWhenCreated {
stopForeground(true) stopForeground(true)
} }
} }
}
private fun handleUpdateAudio(intent: Intent) { private fun handleUpdateAudio(intent: Intent) {
val audioCommand = intent.getParcelableExtra<AudioManagerCommand>(EXTRA_AUDIO_COMMAND)!! val audioCommand = intent.getParcelableExtra<AudioManagerCommand>(EXTRA_AUDIO_COMMAND)!!
@ -409,6 +420,11 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
callManager.initializeAudioForCall() callManager.initializeAudioForCall()
callManager.startIncomingRinger() callManager.startIncomingRinger()
callManager.setAudioEnabled(true) callManager.setAudioEnabled(true)
BackgroundPollWorker.scheduleOnce(
this,
arrayOf(BackgroundPollWorker.Targets.DMS)
)
} }
} }
@ -573,7 +589,9 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
private fun handleRemoteHangup(intent: Intent) { private fun handleRemoteHangup(intent: Intent) {
if (callManager.callId != getCallId(intent)) { if (callManager.callId != getCallId(intent)) {
Log.e(TAG, "Hangup for non-active call...") Log.e(TAG, "Hangup for non-active call...")
lifecycleScope.launchWhenCreated {
stopForeground(true) stopForeground(true)
}
return return
} }
@ -717,10 +735,16 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
} }
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) { private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
try {
startForeground( startForeground(
CallNotificationBuilder.WEBRTC_NOTIFICATION, CallNotificationBuilder.WEBRTC_NOTIFICATION,
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
) )
}
catch(e: ForegroundServiceStartNotAllowedException) {
Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead")
}
if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) { if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
// start an intent for the fullscreen // start an intent for the fullscreen
val foregroundIntent = Intent(this, WebRtcCallActivity::class.java) val foregroundIntent = Intent(this, WebRtcCallActivity::class.java)
@ -769,10 +793,14 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener {
callReceiver?.let { receiver -> callReceiver?.let { receiver ->
unregisterReceiver(receiver) unregisterReceiver(receiver)
} }
wiredHeadsetStateReceiver?.let { unregisterReceiver(it) }
powerButtonReceiver?.let { unregisterReceiver(it) }
networkChangedReceiver?.unregister(this) networkChangedReceiver?.unregister(this)
wantsToAnswerReceiver?.let { receiver -> wantsToAnswerReceiver?.let { receiver ->
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
} }
powerButtonReceiver = null
wiredHeadsetStateReceiver = null
networkChangedReceiver = null networkChangedReceiver = null
callReceiver = null callReceiver = null
uncaughtExceptionHandlerManager?.unregister() uncaughtExceptionHandlerManager?.unregister()

View File

@ -18,6 +18,7 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Debouncer import org.session.libsession.utilities.Debouncer
import org.session.libsession.utilities.Util import org.session.libsession.utilities.Util
@ -574,7 +575,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
} }
} }
fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, signal: Boolean = false, sentTimestamp: Long = System.currentTimeMillis()) { fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, signal: Boolean = false, sentTimestamp: Long = SnodeAPI.nowWithOffset) {
storage.insertCallMessage(threadPublicKey, callMessageType, sentTimestamp) storage.insertCallMessage(threadPublicKey, callMessageType, sentTimestamp)
} }

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.webrtc
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
@ -32,6 +33,20 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
companion object { companion object {
private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L
fun safeStartService(context: Context, intent: Intent) {
// If the foreground service crashes then it's possible for one of these intents to
// be started in the background (in which case 'startService' will throw a
// 'BackgroundServiceStartNotAllowedException' exception) so catch that case and try
// to re-start the service in the foreground
try { context.startService(intent) }
catch(e: Exception) {
try { ContextCompat.startForegroundService(context, intent) }
catch (e2: Exception) {
Log.e("Loki", "Unable to start CallMessage intent: ${e2.message}")
}
}
}
} }
init { init {
@ -90,7 +105,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
private fun incomingHangup(callMessage: CallMessage) { private fun incomingHangup(callMessage: CallMessage) {
val callId = callMessage.callId ?: return val callId = callMessage.callId ?: return
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
context.startService(hangupIntent) safeStartService(context, hangupIntent)
} }
private fun incomingAnswer(callMessage: CallMessage) { private fun incomingAnswer(callMessage: CallMessage) {
@ -103,7 +118,8 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
sdp = sdp, sdp = sdp,
callId = callId callId = callId
) )
context.startService(answerIntent)
safeStartService(context, answerIntent)
} }
private fun handleIceCandidates(callMessage: CallMessage) { private fun handleIceCandidates(callMessage: CallMessage) {
@ -119,7 +135,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId, callId = callId,
address = Address.fromSerialized(sender) address = Address.fromSerialized(sender)
) )
context.startService(iceIntent) safeStartService(context, iceIntent)
} }
private fun incomingPreOffer(callMessage: CallMessage) { private fun incomingPreOffer(callMessage: CallMessage) {
@ -132,7 +148,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId, callId = callId,
callTime = callMessage.sentTimestamp!! callTime = callMessage.sentTimestamp!!
) )
context.startService(incomingIntent) safeStartService(context, incomingIntent)
} }
private fun incomingCall(callMessage: CallMessage) { private fun incomingCall(callMessage: CallMessage) {
@ -146,7 +162,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId, callId = callId,
callTime = callMessage.sentTimestamp!! callTime = callMessage.sentTimestamp!!
) )
context.startService(incomingIntent) safeStartService(context, incomingIntent)
} }
private fun CallMessage.iceCandidates(): List<IceCandidate> { private fun CallMessage.iceCandidates(): List<IceCandidate> {

View File

@ -98,19 +98,19 @@ class BatchMessageReceiveJob(
} catch (e: Exception) { } catch (e: Exception) {
when (e) { when (e) {
is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> { is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> {
Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}") Log.i(TAG, "Couldn't receive message, failed with error: ${e.message} (id: $id)")
} }
is MessageReceiver.Error -> { is MessageReceiver.Error -> {
if (!e.isRetryable) { if (!e.isRetryable) {
Log.e(TAG, "Couldn't receive message, failed permanently", e) Log.e(TAG, "Couldn't receive message, failed permanently (id: $id)", e)
} }
else { else {
Log.e(TAG, "Couldn't receive message, failed", e) Log.e(TAG, "Couldn't receive message, failed (id: $id)", e)
failures += messageParameters failures += messageParameters
} }
} }
else -> { else -> {
Log.e(TAG, "Couldn't receive message, failed", e) Log.e(TAG, "Couldn't receive message, failed (id: $id)", e)
failures += messageParameters failures += messageParameters
} }
} }
@ -166,11 +166,11 @@ class BatchMessageReceiveJob(
else -> MessageReceiver.handle(message, proto, openGroupID) else -> MessageReceiver.handle(message, proto, openGroupID)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Couldn't process message.", e) Log.e(TAG, "Couldn't process message (id: $id)", e)
if (e is MessageReceiver.Error && !e.isRetryable) { if (e is MessageReceiver.Error && !e.isRetryable) {
Log.e(TAG, "Message failed permanently",e) Log.e(TAG, "Message failed permanently (id: $id)", e)
} else { } else {
Log.e(TAG, "Message failed",e) Log.e(TAG, "Message failed (id: $id)", e)
failures += parameters failures += parameters
} }
} }
@ -199,12 +199,12 @@ class BatchMessageReceiveJob(
} }
private fun handleSuccess(dispatcherName: String) { private fun handleSuccess(dispatcherName: String) {
Log.i(TAG, "Completed processing of ${messages.size} messages") Log.i(TAG, "Completed processing of ${messages.size} messages (id: $id)")
this.delegate?.handleJobSucceeded(this, dispatcherName) this.delegate?.handleJobSucceeded(this, dispatcherName)
} }
private fun handleFailure(dispatcherName: String) { private fun handleFailure(dispatcherName: String) {
Log.i(TAG, "Handling failure of ${failures.size} messages (${messages.size - failures.size} processed successfully)") Log.i(TAG, "Handling failure of ${failures.size} messages (${messages.size - failures.size} processed successfully) (id: $id)")
this.delegate?.handleJobFailed(this, dispatcherName, Exception("One or more jobs resulted in failure")) this.delegate?.handleJobFailed(this, dispatcherName, Exception("One or more jobs resulted in failure"))
} }

View File

@ -3,6 +3,7 @@ package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.Data
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: String?) : Job { class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: String?) : Job {
@ -43,7 +44,7 @@ class GroupAvatarDownloadJob(val server: String, val room: String, val imageId:
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.updateProfilePicture(groupId, bytes) storage.updateProfilePicture(groupId, bytes)
storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) storage.updateTimestampUpdated(groupId, SnodeAPI.nowWithOffset)
delegate?.handleJobSucceeded(this, dispatcherName) delegate?.handleJobSucceeded(this, dispatcherName)
} catch (e: Exception) { } catch (e: Exception) {
delegate?.handleJobFailed(this, dispatcherName, e) delegate?.handleJobFailed(this, dispatcherName, e)

View File

@ -23,6 +23,7 @@ import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.OnionResponse import org.session.libsession.snode.OnionResponse
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Base64.decode import org.session.libsignal.utilities.Base64.decode
import org.session.libsignal.utilities.Base64.encodeBytes import org.session.libsignal.utilities.Base64.encodeBytes
@ -303,7 +304,7 @@ object OpenGroupApi {
val headers = request.headers.toMutableMap() val headers = request.headers.toMutableMap()
if (request.isAuthRequired) { if (request.isAuthRequired) {
val nonce = sodium.nonce(16) val nonce = sodium.nonce(16)
val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
var pubKey = "" var pubKey = ""
var signature = ByteArray(Sign.BYTES) var signature = ByteArray(Sign.BYTES)
var bodyHash = ByteArray(0) var bodyHash = ByteArray(0)

View File

@ -15,6 +15,7 @@ import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.SnodeAPI
import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.crypto.PushTransportDetails
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
@ -156,7 +157,7 @@ object MessageReceiver {
message.sender = sender message.sender = sender
message.recipient = userPublicKey message.recipient = userPublicKey
message.sentTimestamp = envelope.timestamp message.sentTimestamp = envelope.timestamp
message.receivedTimestamp = if (envelope.hasServerTimestamp()) envelope.serverTimestamp else System.currentTimeMillis() message.receivedTimestamp = if (envelope.hasServerTimestamp()) envelope.serverTimestamp else SnodeAPI.nowWithOffset
message.groupPublicKey = groupPublicKey message.groupPublicKey = groupPublicKey
message.openGroupServerMessageID = openGroupServerID message.openGroupServerMessageID = openGroupServerID
// Validate // Validate

View File

@ -199,7 +199,20 @@ object MessageSender {
val hash = it["hash"] as? String val hash = it["hash"] as? String
message.serverHash = hash message.serverHash = hash
handleSuccessfulMessageSend(message, destination, isSyncMessage) handleSuccessfulMessageSend(message, destination, isSyncMessage)
val shouldNotify = ((message is VisibleMessage || message is UnsendRequest || message is CallMessage) && !isSyncMessage)
val shouldNotify: Boolean = when (message) {
is VisibleMessage, is UnsendRequest -> !isSyncMessage
is CallMessage -> {
// Note: Other 'CallMessage' types are too big to send as push notifications
// so only send the 'preOffer' message as a notification
when (message.type) {
SignalServiceProtos.CallMessage.Type.PRE_OFFER -> true
else -> false
}
}
else -> false
}
/* /*
if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) {
shouldNotify = true shouldNotify = true
@ -229,7 +242,7 @@ object MessageSender {
val deferred = deferred<Unit, Exception>() val deferred = deferred<Unit, Exception>()
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
if (message.sentTimestamp == null) { if (message.sentTimestamp == null) {
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = SnodeAPI.nowWithOffset
} }
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!! val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!
var serverCapabilities = listOf<String>() var serverCapabilities = listOf<String>()

View File

@ -50,11 +50,11 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
val admins = setOf( userPublicKey ) val admins = setOf( userPublicKey )
val adminsAsData = admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } val adminsAsData = admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
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) }), System.currentTimeMillis()) null, null, LinkedList(admins.map { Address.fromSerialized(it) }), SnodeAPI.nowWithOffset)
storage.setProfileSharing(Address.fromSerialized(groupID), true) storage.setProfileSharing(Address.fromSerialized(groupID), true)
// Send a closed group update message to all members individually // Send a closed group update message to all members individually
val closedGroupUpdateKind = ClosedGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData, 0) val closedGroupUpdateKind = ClosedGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData, 0)
val sentTime = System.currentTimeMillis() val sentTime = SnodeAPI.nowWithOffset
for (member in members) { for (member in members) {
val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind) val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime closedGroupControlMessage.sentTimestamp = sentTime
@ -97,7 +97,7 @@ fun MessageSender.setName(groupPublicKey: String, newName: String) {
val admins = group.admins.map { it.serialize() } val admins = group.admins.map { it.serialize() }
// Send the update to the group // Send the update to the group
val kind = ClosedGroupControlMessage.Kind.NameChange(newName) val kind = ClosedGroupControlMessage.Kind.NameChange(newName)
val sentTime = System.currentTimeMillis() val sentTime = SnodeAPI.nowWithOffset
val closedGroupControlMessage = ClosedGroupControlMessage(kind) val closedGroupControlMessage = ClosedGroupControlMessage(kind)
closedGroupControlMessage.sentTimestamp = sentTime closedGroupControlMessage.sentTimestamp = sentTime
send(closedGroupControlMessage, Address.fromSerialized(groupID)) send(closedGroupControlMessage, Address.fromSerialized(groupID))
@ -137,7 +137,7 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>)
val name = group.title val name = group.title
// Send the update to the group // Send the update to the group
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersAdded(newMembersAsData) val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersAdded(newMembersAsData)
val sentTime = System.currentTimeMillis() val sentTime = SnodeAPI.nowWithOffset
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind) val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime closedGroupControlMessage.sentTimestamp = sentTime
send(closedGroupControlMessage, Address.fromSerialized(groupID)) send(closedGroupControlMessage, Address.fromSerialized(groupID))
@ -151,7 +151,7 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>)
// updates from before that timestamp. By setting the timestamp of the message below to a value // updates from before that timestamp. By setting the timestamp of the message below to a value
// greater than that of the `MembersAdded` message, we ensure that newly added members ignore // greater than that of the `MembersAdded` message, we ensure that newly added members ignore
// the `MembersAdded` message. // the `MembersAdded` message.
closedGroupControlMessage.sentTimestamp = System.currentTimeMillis() closedGroupControlMessage.sentTimestamp = SnodeAPI.nowWithOffset
send(closedGroupControlMessage, Address.fromSerialized(member)) send(closedGroupControlMessage, Address.fromSerialized(member))
} }
// Notify the user // Notify the user
@ -192,7 +192,7 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
val name = group.title val name = group.title
// Send the update to the group // Send the update to the group
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData) val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData)
val sentTime = System.currentTimeMillis() val sentTime = SnodeAPI.nowWithOffset
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind) val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime closedGroupControlMessage.sentTimestamp = sentTime
send(closedGroupControlMessage, Address.fromSerialized(groupID)) send(closedGroupControlMessage, Address.fromSerialized(groupID))
@ -223,7 +223,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro
val name = group.title val name = group.title
// Send the update to the group // Send the update to the group
val closedGroupControlMessage = ClosedGroupControlMessage(ClosedGroupControlMessage.Kind.MemberLeft()) val closedGroupControlMessage = ClosedGroupControlMessage(ClosedGroupControlMessage.Kind.MemberLeft())
val sentTime = System.currentTimeMillis() val sentTime = SnodeAPI.nowWithOffset
closedGroupControlMessage.sentTimestamp = sentTime closedGroupControlMessage.sentTimestamp = sentTime
storage.setActive(groupID, false) storage.setActive(groupID, false)
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success { sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success {

View File

@ -54,10 +54,9 @@ class ClosedGroupPollerV2 {
setUpPolling(groupPublicKey) setUpPolling(groupPublicKey)
} }
fun stop() { fun stopAll() {
val storage = MessagingModuleConfiguration.shared.storage futures.forEach { it.value.cancel(false) }
val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys() isPolling.forEach { isPolling[it.key] = false }
allGroupPublicKeys.iterator().forEach { stopPolling(it) }
} }
fun stopPolling(groupPublicKey: String) { fun stopPolling(groupPublicKey: String) {

View File

@ -26,6 +26,7 @@ import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.utilities.recover import org.session.libsignal.utilities.recover
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import java.util.Date import java.util.Date
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.set import kotlin.collections.set
private typealias Path = List<Snode> private typealias Path = List<Snode>
@ -43,13 +44,27 @@ object OnionRequestAPI {
private val snodeFailureCount = mutableMapOf<Snode, Int>() private val snodeFailureCount = mutableMapOf<Snode, Int>()
var guardSnodes = setOf<Snode>() var guardSnodes = setOf<Snode>()
var _paths: AtomicReference<List<Path>?> = AtomicReference(null)
var paths: List<Path> // Not a set to ensure we consistently show the same path to the user var paths: List<Path> // Not a set to ensure we consistently show the same path to the user
get() = database.getOnionRequestPaths() get() {
val paths = _paths.get()
if (paths != null) { return paths }
// Storing this in an atomic variable as it was causing a number of background
// ANRs when this value was accessed via the main thread after tapping on
// a notification)
val result = database.getOnionRequestPaths()
_paths.set(result)
return result
}
set(newValue) { set(newValue) {
if (newValue.isEmpty()) { if (newValue.isEmpty()) {
database.clearOnionRequestPaths() database.clearOnionRequestPaths()
_paths.set(null)
} else { } else {
database.setOnionRequestPaths(newValue) database.setOnionRequestPaths(newValue)
_paths.set(newValue)
} }
} }

View File

@ -57,7 +57,7 @@ object SnodeAPI {
internal var clockOffset = 0L internal var clockOffset = 0L
@JvmStatic @JvmStatic
val nowWithOffset public val nowWithOffset
get() = System.currentTimeMillis() + clockOffset get() = System.currentTimeMillis() + clockOffset
internal var forkInfo by observable(database.getForkInfo()) { _, oldValue, newValue -> internal var forkInfo by observable(database.getForkInfo()) { _, oldValue, newValue ->
@ -590,7 +590,7 @@ object SnodeAPI {
val parameters = message.toJSON().toMutableMap<String,Any>() val parameters = message.toJSON().toMutableMap<String,Any>()
// Construct signature // Construct signature
if (requiresAuth) { if (requiresAuth) {
val sigTimestamp = System.currentTimeMillis() + SnodeAPI.clockOffset val sigTimestamp = nowWithOffset
val ed25519PublicKey = userED25519KeyPair.publicKey.asHexString val ed25519PublicKey = userED25519KeyPair.publicKey.asHexString
val signature = ByteArray(Sign.BYTES) val signature = ByteArray(Sign.BYTES)
// assume namespace here is non-zero, as zero namespace doesn't require auth // assume namespace here is non-zero, as zero namespace doesn't require auth

View File

@ -26,7 +26,7 @@ class SSKEnvironment(
interface ProfileManagerProtocol { interface ProfileManagerProtocol {
companion object { companion object {
const val NAME_PADDED_LENGTH = 26 const val NAME_PADDED_LENGTH = 64
} }
fun setNickname(context: Context, recipient: Recipient, nickname: String?) fun setNickname(context: Context, recipient: Recipient, nickname: String?)

View File

@ -319,8 +319,8 @@ public class SignalServiceDataMessage {
return this; return this;
} }
public SignalServiceDataMessage build() { public SignalServiceDataMessage build(long fallbackTimestamp) {
if (timestamp == 0) timestamp = System.currentTimeMillis(); if (timestamp == 0) timestamp = fallbackTimestamp;
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews,
null, syncTarget); null, syncTarget);