Perform safety number check pre-send in CFv2.
This commit is contained in:
parent
82b3036b77
commit
1af50ba0f5
5 changed files with 106 additions and 22 deletions
|
@ -125,6 +125,7 @@ import org.thoughtcrime.securesms.components.settings.conversation.ConversationS
|
|||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.contactshare.ContactUtil
|
||||
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity
|
||||
|
@ -327,7 +328,8 @@ class ConversationFragment :
|
|||
EmojiSearchFragment.Callback,
|
||||
ScheduleMessageTimePickerBottomSheet.ScheduleCallback,
|
||||
ScheduleMessageDialogCallback,
|
||||
ConversationBottomSheetCallback {
|
||||
ConversationBottomSheetCallback,
|
||||
SafetyNumberBottomSheet.Callbacks {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||
|
@ -553,7 +555,7 @@ class ConversationFragment :
|
|||
|
||||
motionEventRelay.setDrain(MotionEventRelayDrain())
|
||||
|
||||
viewModel.updateIdentityRecords()
|
||||
viewModel.updateIdentityRecordsInBackground()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -712,6 +714,24 @@ class ConversationFragment :
|
|||
sendMessage(scheduledDate = scheduledDate)
|
||||
}
|
||||
|
||||
override fun sendAnywayAfterSafetyNumberChangedInBottomSheet(destinations: List<RecipientSearchKey>) {
|
||||
Log.d(TAG, "onSendAnywayAfterSafetyNumberChange")
|
||||
viewModel
|
||||
.updateIdentityRecords()
|
||||
.subscribeBy(
|
||||
onError = { t -> Log.w(TAG, "Error sending", t) },
|
||||
onComplete = { sendMessage() }
|
||||
)
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() {
|
||||
Log.d(TAG, "onMessageResentAfterSafetyNumberChange")
|
||||
viewModel.updateIdentityRecordsInBackground()
|
||||
}
|
||||
|
||||
override fun onCanceled() = Unit
|
||||
|
||||
//endregion
|
||||
|
||||
private fun observeConversationThread() {
|
||||
|
@ -859,6 +879,7 @@ class ConversationFragment :
|
|||
|
||||
viewModel
|
||||
.identityRecords
|
||||
.distinctUntilChanged()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy { presentIdentityRecordsState(it) }
|
||||
.addTo(disposables)
|
||||
|
@ -1574,7 +1595,8 @@ class ConversationFragment :
|
|||
bodyRanges = null,
|
||||
messageToEdit = null,
|
||||
quote = null,
|
||||
linkPreviews = emptyList()
|
||||
linkPreviews = emptyList(),
|
||||
bypassPreSendSafetyNumberCheck = true
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1590,6 +1612,7 @@ class ConversationFragment :
|
|||
clearCompose: Boolean = true,
|
||||
linkPreviews: List<LinkPreview> = linkPreviewViewModel.onSend(),
|
||||
preUploadResults: List<MessageSender.PreUploadResult> = emptyList(),
|
||||
bypassPreSendSafetyNumberCheck: Boolean = false,
|
||||
afterSendComplete: () -> Unit = {}
|
||||
) {
|
||||
if (scheduledDate != -1L && ReenableScheduledMessagesDialogFragment.showIfNeeded(requireContext(), childFragmentManager, null, scheduledDate)) {
|
||||
|
@ -1622,25 +1645,26 @@ class ConversationFragment :
|
|||
bodyRanges = bodyRanges,
|
||||
contacts = contacts,
|
||||
linkPreviews = linkPreviews,
|
||||
preUploadResults = preUploadResults
|
||||
preUploadResults = preUploadResults,
|
||||
bypassPreSendSafetyNumberCheck = bypassPreSendSafetyNumberCheck
|
||||
)
|
||||
|
||||
disposables += send
|
||||
.doOnSubscribe {
|
||||
if (clearCompose) {
|
||||
composeText.setText("")
|
||||
inputPanel.clearQuote()
|
||||
}
|
||||
}
|
||||
.subscribeBy(
|
||||
onError = { t ->
|
||||
Log.w(TAG, "Error sending", t)
|
||||
when (t) {
|
||||
is InvalidMessageException -> toast(R.string.ConversationActivity_message_is_empty_exclamation)
|
||||
is RecipientFormattingException -> toast(R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation, Toast.LENGTH_LONG)
|
||||
is RecentSafetyNumberChangeException -> handleRecentSafetyNumberChange(t.changedRecords)
|
||||
}
|
||||
},
|
||||
onComplete = {
|
||||
if (clearCompose) {
|
||||
composeText.setText("")
|
||||
inputPanel.clearQuote()
|
||||
}
|
||||
|
||||
onSendComplete()
|
||||
afterSendComplete()
|
||||
}
|
||||
|
@ -1667,6 +1691,13 @@ class ConversationFragment :
|
|||
inputPanel.exitEditMessageMode()
|
||||
}
|
||||
|
||||
private fun handleRecentSafetyNumberChange(changedRecords: List<IdentityRecord>) {
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
SafetyNumberBottomSheet
|
||||
.forIdentityRecordsAndDestination(changedRecords, RecipientSearchKey(recipient.getId(), false))
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
private fun toast(@StringRes toastTextId: Int, toastDuration: Int = Toast.LENGTH_SHORT) {
|
||||
ThreadUtil.runOnMain {
|
||||
if (context != null) {
|
||||
|
@ -3617,7 +3648,7 @@ class ConversationFragment :
|
|||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
fun onIdentityRecordUpdate(event: IdentityRecord?) {
|
||||
viewModel.updateIdentityRecords()
|
||||
viewModel.updateIdentityRecordsInBackground()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
|
|
|
@ -203,7 +203,8 @@ class ConversationRepository(
|
|||
bodyRanges: BodyRangeList?,
|
||||
contacts: List<Contact>,
|
||||
linkPreviews: List<LinkPreview>,
|
||||
preUploadResults: List<PreUploadResult>
|
||||
preUploadResults: List<PreUploadResult>,
|
||||
identityRecordsState: IdentityRecordsState?
|
||||
): Completable {
|
||||
val sendCompletable = Completable.create { emitter ->
|
||||
if (body.isEmpty() && slideDeck?.containsMediaSlide() != true && preUploadResults.isEmpty()) {
|
||||
|
@ -216,6 +217,11 @@ class ConversationRepository(
|
|||
return@create
|
||||
}
|
||||
|
||||
if (identityRecordsState != null && identityRecordsState.hasRecentSafetyNumberChange()) {
|
||||
emitter.onError(RecentSafetyNumberChangeException(identityRecordsState.getRecentSafetyNumberChangeRecords()))
|
||||
return@create
|
||||
}
|
||||
|
||||
val splitMessage: MessageUtil.SplitResult = MessageUtil.getSplitMessage(
|
||||
applicationContext,
|
||||
body,
|
||||
|
@ -369,7 +375,7 @@ class ConversationRepository(
|
|||
records.isVerified &&
|
||||
!recipient.isSelf
|
||||
|
||||
IdentityRecordsState(isVerified, records, isGroup = groupRecord != null)
|
||||
IdentityRecordsState(recipient, groupRecord, isVerified, records, isGroup = groupRecord != null)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,8 @@ class ConversationViewModel(
|
|||
val reminder: Observable<Optional<Reminder>>
|
||||
|
||||
private val refreshIdentityRecords: Subject<Unit> = PublishSubject.create()
|
||||
val identityRecords: Observable<IdentityRecordsState>
|
||||
private val identityRecordsStore: RxStore<IdentityRecordsState> = RxStore(IdentityRecordsState())
|
||||
val identityRecords: Observable<IdentityRecordsState> = identityRecordsStore.stateFlowable.toObservable()
|
||||
|
||||
private val _searchQuery = BehaviorSubject.createDefault("")
|
||||
val searchQuery: Observable<String> = _searchQuery
|
||||
|
@ -218,13 +219,17 @@ class ConversationViewModel(
|
|||
.flatMapMaybe { groupRecord -> repository.getReminder(groupRecord.orNull()) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
identityRecords = Observable.combineLatest(
|
||||
Observable.combineLatest(
|
||||
refreshIdentityRecords.startWithItem(Unit).observeOn(Schedulers.io()),
|
||||
recipient,
|
||||
recipientRepository.groupRecord
|
||||
) { _, r, g -> Pair(r, g) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMapSingle { (r, g) -> repository.getIdentityRecords(r, g.orNull()) }
|
||||
.distinctUntilChanged()
|
||||
.subscribeBy { newState ->
|
||||
identityRecordsStore.update { newState }
|
||||
}
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
fun setSearchQuery(query: String?) {
|
||||
|
@ -327,7 +332,8 @@ class ConversationViewModel(
|
|||
bodyRanges: BodyRangeList?,
|
||||
contacts: List<Contact>,
|
||||
linkPreviews: List<LinkPreview>,
|
||||
preUploadResults: List<MessageSender.PreUploadResult>
|
||||
preUploadResults: List<MessageSender.PreUploadResult>,
|
||||
bypassPreSendSafetyNumberCheck: Boolean
|
||||
): Completable {
|
||||
return repository.sendMessage(
|
||||
threadId = threadId,
|
||||
|
@ -342,7 +348,8 @@ class ConversationViewModel(
|
|||
bodyRanges = bodyRanges,
|
||||
contacts = contacts,
|
||||
linkPreviews = linkPreviews,
|
||||
preUploadResults = preUploadResults
|
||||
preUploadResults = preUploadResults,
|
||||
identityRecordsState = if (bypassPreSendSafetyNumberCheck) null else identityRecordsStore.state
|
||||
).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
|
@ -353,10 +360,24 @@ class ConversationViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateIdentityRecords() {
|
||||
fun updateIdentityRecordsInBackground() {
|
||||
refreshIdentityRecords.onNext(Unit)
|
||||
}
|
||||
|
||||
fun updateIdentityRecords(): Completable {
|
||||
val state: IdentityRecordsState = identityRecordsStore.state
|
||||
if (state.recipient == null) {
|
||||
return Completable.error(IllegalStateException("No recipient in records store"))
|
||||
}
|
||||
|
||||
return repository.getIdentityRecords(state.recipient, state.group)
|
||||
.doOnSuccess { newState ->
|
||||
identityRecordsStore.update { newState }
|
||||
}
|
||||
.flatMapCompletable { Completable.complete() }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun getTemporaryViewOnceUri(mmsMessageRecord: MmsMessageRecord): Maybe<Uri> {
|
||||
return repository.getTemporaryViewOnceUri(mmsMessageRecord).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
|
|
@ -6,14 +6,27 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
/**
|
||||
* Current state for all participants identity keys in a conversation excluding self.
|
||||
*/
|
||||
data class IdentityRecordsState(
|
||||
val isVerified: Boolean,
|
||||
val identityRecords: IdentityRecordList,
|
||||
val isGroup: Boolean
|
||||
val recipient: Recipient? = null,
|
||||
val group: GroupRecord? = null,
|
||||
val isVerified: Boolean = false,
|
||||
val identityRecords: IdentityRecordList = IdentityRecordList(emptyList()),
|
||||
val isGroup: Boolean = false
|
||||
) {
|
||||
val isUnverified: Boolean = identityRecords.isUnverified
|
||||
|
||||
fun hasRecentSafetyNumberChange(): Boolean {
|
||||
return identityRecords.isUnverified(true) || identityRecords.isUntrusted(true)
|
||||
}
|
||||
|
||||
fun getRecentSafetyNumberChangeRecords(): List<IdentityRecord> {
|
||||
return identityRecords.unverifiedRecords + identityRecords.untrustedRecords
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
|
||||
/**
|
||||
* Emitted when safety numbers changed recently before a send attempt.
|
||||
*/
|
||||
class RecentSafetyNumberChangeException(val changedRecords: List<IdentityRecord>) : Exception()
|
Loading…
Add table
Reference in a new issue