Skip to content

Commit 712b2f5

Browse files
authored
SES-5135 : Chat with unknown group member - Misplaced control messages (#1844)
* Updated how threadId is fetched in insertUpdateControlMessage, default threadId to 0L for convo * cleanup * Added support for null threadId * Revert conversation loader to take non-null Long * onCreateLoader param * Run cursor but return empty if threadId is null * removed getThreadIdIfExistsFor wrapper, updated some function calls to use storage
1 parent 38ead5d commit 712b2f5

15 files changed

Lines changed: 166 additions & 115 deletions

File tree

app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ class ReceivedMessageProcessor @Inject constructor(
153153
threadDatabase.getOrCreateThreadIdFor(threadAddress)
154154
.also { context.threadIDs[threadAddress] = it }
155155
} else {
156-
threadDatabase.getThreadIdIfExistsFor(threadAddress)
156+
storage.getThreadId(threadAddress)
157157
.also { id ->
158-
if (id == -1L) {
158+
if (id == null) {
159159
log { "Dropping message for non-existing thread ${threadAddress.debugString}" }
160160
return@withThreadLock
161161
} else {
@@ -200,16 +200,18 @@ class ReceivedMessageProcessor @Inject constructor(
200200
context.maxOutgoingMessageTimestamp = message.sentTimestamp!!
201201
}
202202

203-
visibleMessageHandler.get().handleVisibleMessage(
204-
ctx = context,
205-
message = message,
206-
threadId = threadId,
207-
threadAddress = threadAddress,
208-
proto = proto,
209-
runThreadUpdate = false,
210-
runProfileUpdate = true,
211-
pro = pro,
212-
)
203+
threadId?.let {
204+
visibleMessageHandler.get().handleVisibleMessage(
205+
ctx = context,
206+
message = message,
207+
threadId = it,
208+
threadAddress = threadAddress,
209+
proto = proto,
210+
runThreadUpdate = false,
211+
runProfileUpdate = true,
212+
pro = pro,
213+
)
214+
}
213215
}
214216

215217
is CallMessage -> handleCallMessage(message)

app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,9 @@ class ConfigToDatabaseSync @Inject constructor(
347347
}
348348
}
349349

350-
val threadId = threadDatabase.getThreadIdIfExistsFor(address)
350+
val threadId = storage.getThreadId(address)
351351

352-
if (threadId != -1L) {
352+
if (threadId != null) {
353353
if (conversation.lastRead > storage.getLastSeen(threadId)) {
354354
storage.markConversationAsRead(
355355
threadId,

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ import androidx.core.view.isVisible
4545
import androidx.core.view.updateLayoutParams
4646
import androidx.fragment.app.DialogFragment
4747
import androidx.lifecycle.Lifecycle
48+
import androidx.lifecycle.LiveData
4849
import androidx.lifecycle.Observer
4950
import androidx.lifecycle.ViewModelProvider
5051
import androidx.lifecycle.flowWithLifecycle
5152
import androidx.lifecycle.lifecycleScope
5253
import androidx.lifecycle.repeatOnLifecycle
54+
import androidx.lifecycle.viewModelScope
5355
import androidx.loader.app.LoaderManager
5456
import androidx.loader.content.Loader
5557
import androidx.recyclerview.widget.LinearLayoutManager
@@ -76,6 +78,7 @@ import kotlinx.coroutines.flow.filterNotNull
7678
import kotlinx.coroutines.flow.first
7779
import kotlinx.coroutines.flow.map
7880
import kotlinx.coroutines.flow.mapNotNull
81+
import kotlinx.coroutines.flow.onStart
7982
import kotlinx.coroutines.flow.receiveAsFlow
8083
import kotlinx.coroutines.launch
8184
import kotlinx.coroutines.withContext
@@ -378,7 +381,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
378381
private val adapter by lazy {
379382
val adapter = ConversationAdapter(
380383
this,
381-
storage.getLastSeen(viewModel.threadId),
384+
originalLastSeen = viewModel.threadId
385+
?.let { storage.getLastSeen(it) },
382386
false,
383387
onItemPress = { message, position, view, event ->
384388
handlePress(message, position, view, event)
@@ -638,8 +642,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
638642
try {
639643
when (it) {
640644
is Long -> {
641-
if (storage.getLastSeen(viewModel.threadId) < it) {
642-
storage.markConversationAsRead(viewModel.threadId, it)
645+
viewModel.threadId?.let { threadId ->
646+
if (storage.getLastSeen(threadId) < it) {
647+
storage.markConversationAsRead(threadId, it)
648+
}
643649
}
644650
}
645651

@@ -661,8 +667,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
661667
viewModel.conversationReloadNotification
662668
.collect {
663669
if (!firstLoad.get()) {
664-
LoaderManager.getInstance(this@ConversationActivityV2)
665-
.restartLoader(0, null, this@ConversationActivityV2)
670+
restartConversationLoader()
666671
}
667672
}
668673
}
@@ -672,6 +677,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
672677
setupUiEventsObserver()
673678
}
674679

680+
private fun restartConversationLoader() {
681+
LoaderManager.getInstance(this).restartLoader(0, null, this)
682+
}
683+
675684
private fun startConversationLoaderWithDelay() {
676685
conversationLoadAnimationJob = lifecycleScope.launch {
677686
delay(700)
@@ -782,7 +791,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
782791

783792
override fun onResume() {
784793
super.onResume()
785-
messageNotifier.setVisibleThread(viewModel.threadId)
794+
viewModel.threadId?.let { threadId ->
795+
messageNotifier.setVisibleThread(threadId)
796+
}
786797
}
787798

788799
override fun onPause() {
@@ -844,11 +855,13 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
844855
)
845856
}
846857

847-
if (isUnread) {
848-
storage.markConversationAsRead(
849-
viewModel.threadId,
850-
clock.currentTimeMillis()
851-
)
858+
viewModel.threadId?.let { threadId ->
859+
if (isUnread) {
860+
storage.markConversationAsRead(
861+
threadId,
862+
clock.currentTimeMillis()
863+
)
864+
}
852865
}
853866
}
854867
}
@@ -882,7 +895,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
882895
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
883896
binding.conversationRecyclerView.layoutManager = layoutManager
884897
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will)
885-
LoaderManager.getInstance(this).restartLoader(0, null, this)
898+
restartConversationLoader()
886899
binding.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
887900

888901
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@@ -1026,19 +1039,45 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
10261039
}
10271040

10281041
// called from onCreate
1042+
private var typistsLiveData: LiveData<TypingStatusRepository.TypingState>? = null
10291043
private fun setUpTypingObserver() {
1030-
typingStatusRepository.getTypists(viewModel.threadId).observe(this) { state ->
1031-
val recipients = if (state != null) state.typists else listOf()
1032-
// FIXME: Also checking isScrolledToBottom is a quick fix for an issue where the
1033-
// typing indicator overlays the recycler view when scrolled up
1034-
val viewContainer = binding.typingIndicatorViewContainer
1035-
viewContainer.isVisible = recipients.isNotEmpty() && isScrolledToBottom
1036-
viewContainer.setTypists(recipients)
1044+
// Observe typists only when we have a real threadId,
1045+
// and swap if it changes, example: message request was created then accepted
1046+
lifecycleScope.launch {
1047+
repeatOnLifecycle(Lifecycle.State.STARTED) {
1048+
viewModel.threadIdFlow
1049+
.collect { threadId ->
1050+
// Detach any previous observer
1051+
typistsLiveData?.removeObservers(this@ConversationActivityV2)
1052+
typistsLiveData = null
1053+
1054+
if (threadId == null) {
1055+
binding.typingIndicatorViewContainer.isVisible = false
1056+
return@collect
1057+
}
1058+
1059+
// Attach observer for the real threadId
1060+
typistsLiveData =
1061+
typingStatusRepository.getTypists(threadId).also { liveData ->
1062+
liveData.observe(this@ConversationActivityV2) { state ->
1063+
val recipients = state?.typists ?: emptyList()
1064+
1065+
// Quick-fix behavior kept as-is
1066+
val viewContainer = binding.typingIndicatorViewContainer
1067+
viewContainer.isVisible =
1068+
recipients.isNotEmpty() && isScrolledToBottom
1069+
viewContainer.setTypists(recipients)
1070+
}
1071+
}
1072+
}
1073+
}
10371074
}
10381075
if (textSecurePreferences.isTypingIndicatorsEnabled()) {
10391076
binding.inputBar.addTextChangedListener {
1040-
if(it.isNotEmpty()) {
1041-
typingStatusSender.onTypingStarted(viewModel.threadId)
1077+
if (it.isNotEmpty()) {
1078+
viewModel.threadId?.let { threadId ->
1079+
typingStatusSender.onTypingStarted(threadId)
1080+
}
10421081
}
10431082
}
10441083
}
@@ -1241,7 +1280,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
12411280
return
12421281
}
12431282

1244-
val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(viewModel.threadId).first()
1283+
val threadId = viewModel.threadId
1284+
if (threadId == null) return // Maybe don't scroll
1285+
1286+
val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(threadId).first()
12451287
val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return
12461288

12471289
binding.conversationRecyclerView.runWhenLaidOut {
@@ -1250,7 +1292,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
12501292
((layoutManager?.height ?: 0) / 2)
12511293
)
12521294
}
1253-
12541295
}
12551296

12561297
private fun highlightViewAtPosition(position: Int) {
@@ -1847,7 +1888,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
18471888
messageSender.send(reactionMessage, recipient.address)
18481889
}
18491890

1850-
LoaderManager.getInstance(this).restartLoader(0, null, this)
1891+
restartConversationLoader()
18511892
}
18521893
}
18531894

@@ -1903,7 +1944,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
19031944
} else {
19041945
messageSender.send(message, recipient.address)
19051946
}
1906-
LoaderManager.getInstance(this).restartLoader(0, null, this)
1947+
1948+
restartConversationLoader()
19071949
}
19081950
}
19091951

@@ -2197,8 +2239,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
21972239
waitForApprovalJobToBeSubmitted()
21982240
messageSender.send(message, recipient.address)
21992241
}
2200-
// Send a typing stopped message
2201-
typingStatusSender.onTypingStopped(viewModel.threadId)
2242+
2243+
stopTyping()
22022244
return Pair(recipient.address, sentTimestamp)
22032245
}
22042246

@@ -2294,11 +2336,17 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
22942336
}
22952337
}
22962338

2297-
// Send a typing stopped message
2298-
typingStatusSender.onTypingStopped(viewModel.threadId)
2339+
stopTyping()
22992340
return Pair(recipient.address, sentTimestamp)
23002341
}
23012342

2343+
private fun stopTyping(){
2344+
// Send a typing stopped message
2345+
viewModel.threadId?.let { threadId ->
2346+
typingStatusSender.onTypingStopped(threadId)
2347+
}
2348+
}
2349+
23022350
private fun showGIFPicker() {
23032351
val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning()
23042352
if (!hasSeenGIFMetaDataWarning) {
@@ -2849,9 +2897,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
28492897
}
28502898

28512899
fun onSearchQueryUpdated(query: String) {
2852-
binding.searchBottomBar.showLoading()
2853-
searchViewModel.onQueryUpdated(query, viewModel.threadId)
2854-
adapter.onSearchQueryUpdated(query.takeUnless { it.length < 2 })
2900+
viewModel.threadId?.let { threadId ->
2901+
binding.searchBottomBar.showLoading()
2902+
searchViewModel.onQueryUpdated(query, threadId)
2903+
adapter.onSearchQueryUpdated(query.takeUnless { it.length < 2 })
2904+
}
28552905
}
28562906

28572907
override fun onSearchMoveUpPressed() {
@@ -2863,9 +2913,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
28632913
}
28642914

28652915
private fun jumpToMessage(author: Address, timestamp: Long, highlight: Boolean, onMessageNotFound: Runnable?) {
2866-
SimpleTask.run(lifecycle, {
2867-
mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author, false)
2868-
}) { p: Int -> moveToMessagePosition(p, highlight, onMessageNotFound) }
2916+
viewModel.threadId?.let { threadId ->
2917+
SimpleTask.run(lifecycle, {
2918+
mmsSmsDb.getMessagePositionInConversation(threadId, timestamp, author, false)
2919+
}) { p: Int -> moveToMessagePosition(p, highlight, onMessageNotFound) }
2920+
}
28692921
}
28702922

28712923
private fun moveToMessagePosition(position: Int, highlight: Boolean, onMessageNotFound: Runnable?) {

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase
1919
import org.thoughtcrime.securesms.database.model.MessageId
2020
import org.thoughtcrime.securesms.database.model.MessageRecord
2121
import java.util.concurrent.atomic.AtomicLong
22+
import java.util.concurrent.atomic.AtomicReference
2223
import kotlin.math.min
2324

2425
class ConversationAdapter(
2526
context: Context,
26-
originalLastSeen: Long,
27+
originalLastSeen: Long?,
2728
private val isReversed: Boolean,
2829
private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
2930
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit,
@@ -42,7 +43,7 @@ class ConversationAdapter(
4243
private var searchQuery: String? = null
4344
var visibleMessageViewDelegate: VisibleMessageViewDelegate? = null
4445

45-
private val lastSeen = AtomicLong(originalLastSeen)
46+
private val lastSeen : AtomicReference<Long?> = AtomicReference(originalLastSeen)
4647

4748
var lastSentMessageId: MessageId? = null
4849
set(value) {

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationLoader.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
33
import android.app.Application
44
import android.database.ContentObserver
55
import android.database.Cursor
6+
import android.database.MatrixCursor
67
import dagger.assisted.Assisted
78
import dagger.assisted.AssistedFactory
89
import dagger.assisted.AssistedInject
@@ -11,16 +12,22 @@ import org.thoughtcrime.securesms.database.Storage
1112
import org.thoughtcrime.securesms.util.AbstractCursorLoader
1213

1314
class ConversationLoader @AssistedInject constructor(
14-
@Assisted private val threadID: Long,
15+
@Assisted private val threadID: Long?,
1516
@Assisted private val reverse: Boolean,
1617
application: Application,
1718
private val mmsSmsDatabase: MmsSmsDatabase,
1819
) : AbstractCursorLoader<ConversationLoader.Data>(application) {
1920

2021
override fun getData(): Data {
22+
// Return an empty cursor
23+
val id = threadID ?: return Data(
24+
messageCursor = MatrixCursor(emptyArray<String>()),
25+
threadUnreadCount = 0
26+
)
27+
2128
return Data(
22-
messageCursor = mmsSmsDatabase.getConversation(threadID, reverse),
23-
threadUnreadCount = mmsSmsDatabase.getUnreadCount(threadID),
29+
messageCursor = mmsSmsDatabase.getConversation(id, reverse),
30+
threadUnreadCount = mmsSmsDatabase.getUnreadCount(id),
2431
)
2532
}
2633

@@ -37,6 +44,6 @@ class ConversationLoader @AssistedInject constructor(
3744

3845
@AssistedFactory
3946
interface Factory {
40-
fun create(threadID: Long, reverse: Boolean): ConversationLoader
47+
fun create(threadID: Long?, reverse: Boolean): ConversationLoader
4148
}
4249
}

0 commit comments

Comments
 (0)