Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9464cd2
Bump version to 1.28.2
SessionHero01 Oct 8, 2025
3c89197
Adding non penalizing status code so we don't drop snodes for the wro…
ThomasSession Oct 8, 2025
afa4e3a
Merge pull request #1587 from session-foundation/fix/better-onion-err…
ThomasSession Oct 8, 2025
c7dc279
Better error handling for errors in SOGS
ThomasSession Oct 8, 2025
4086837
fix: enhance error handling in onion requests
SessionHero01 Oct 9, 2025
506f3c8
Improve message query (#1588)
SessionHero01 Oct 9, 2025
c53e347
Set message correctly for generic HTTPRequestFailedException
SessionHero01 Oct 9, 2025
3f58027
Insuring a p roper fallback for community icons
ThomasSession Oct 9, 2025
b4de69f
Merge pull request #1589 from session-foundation/fix/better-onion-err…
ThomasSession Oct 9, 2025
d262a9f
SES-4656 - show loader while we are fetching the conversations
ThomasSession Oct 9, 2025
f6ed75e
Merge pull request #1590 from session-foundation/fix/ses-4656-share-l…
ThomasSession Oct 9, 2025
9f5c13c
Merge pull request #1573 from session-foundation/release/1.28.1
SessionHero01 Oct 9, 2025
cd97a3a
Merge remote-tracking branch 'origin/master' into release/1.28.2
SessionHero01 Oct 9, 2025
5fc0f08
Don't listen to ime insets on a screen without input
ThomasSession Oct 9, 2025
ed9d755
Cleaning up logic
ThomasSession Oct 10, 2025
c43c7a2
Fix layout to avoid broken search result titles
ThomasSession Oct 10, 2025
19eb252
Merge pull request #1591 from session-foundation/fix/ses-4657-media-r…
ThomasSession Oct 10, 2025
082fbd3
Use coil to load image avatar (#1592)
SessionHero01 Oct 10, 2025
3965a00
Update build script so that we can produce release APKs without uploa…
SessionHero01 Oct 10, 2025
e016949
Sanitize logging
SessionHero01 Oct 10, 2025
25453b7
Show special icon when notifications are set to private
ThomasSession Oct 12, 2025
4791a6f
Better unknown notification handling
ThomasSession Oct 13, 2025
87ae5f4
Reworked the notification handler to run a 7 days preiodic re-registr…
ThomasSession Oct 13, 2025
cd84660
Improve getQuote performance (#1595)
SessionHero01 Oct 13, 2025
2264286
Fixed the logic
ThomasSession Oct 13, 2025
fc3e6b9
Merge pull request #1596 from session-foundation/feature/ses-4638-pn-…
ThomasSession Oct 13, 2025
3e5f9bd
Updated qr scanning logic
ThomasSession Oct 13, 2025
7b8cfcf
SES-4529 - Making sure the call tone is attached to the correct audio…
ThomasSession Oct 13, 2025
1fbc626
SES-4668 - Do not locally hangup on self-synced answers
ThomasSession Oct 13, 2025
d56a1fb
Merge pull request #1598 from session-foundation/fix/ses-4529-call-ri…
ThomasSession Oct 13, 2025
a358d78
Unused class cleanup
ThomasSession Oct 13, 2025
a9f56d4
returning true
ThomasSession Oct 13, 2025
2ee740b
Added comments
ThomasSession Oct 13, 2025
0b318db
Merge pull request #1597 from session-foundation/fix/ses-4663-better-…
ThomasSession Oct 13, 2025
8ef6792
Merge pull request #1605 from session-foundation/fix/store-issues
ThomasSession Oct 13, 2025
1de41af
SES-4676 - Fix up vibration for incoming call ring
ThomasSession Oct 14, 2025
1e05b7a
Do not crash in conversation without an address, instead take the use…
ThomasSession Oct 14, 2025
3ef4235
Make sure we reset our observable prefs
ThomasSession Oct 14, 2025
56e1c4c
typo and error refining
ThomasSession Oct 14, 2025
1153675
We shouldn't penalise a 400 from a SOGS
ThomasSession Oct 14, 2025
480523d
PR feedback
ThomasSession Oct 15, 2025
2e5994a
Merge pull request #1607 from session-foundation/ses-4676-call-vibrate
ThomasSession Oct 15, 2025
d7e929f
Merge branch 'release/1.28.2' into merge/1.28.2
ThomasSession Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ configurations.configureEach {
exclude(module = "commons-logging")
}

val canonicalVersionCode = 426
val canonicalVersionName = "1.28.1"
val canonicalVersionCode = 427
val canonicalVersionName = "1.28.2"

val postFixSize = 10
val abiPostFix = mapOf(
Expand Down Expand Up @@ -402,6 +402,7 @@ dependencies {
implementation(libs.phrase)
implementation(libs.copper.flow)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.kovenant)
implementation(libs.kovenant.android)
implementation(libs.opencsv)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ interface MessageDataProvider {
fun isDeletedMessage(id: MessageId): Boolean
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
fun handleFailedAttachmentUpload(attachmentId: Long)
fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>?
fun getMessageForQuote(threadId: Long, timestamp: Long, author: Address): Triple<Long, Boolean, String>?
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment>
fun getMessageBodyFor(timestamp: Long, author: String): String
fun getAttachmentIDsFor(mmsMessageId: Long): List<Long>
fun getLinkPreviewAttachmentIDFor(mmsMessageId: Long): Long?
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ interface StorageProtocol {
fun addReceivedMessageTimestamp(timestamp: Long)
fun removeReceivedMessageTimestamps(timestamps: Set<Long>)
fun getAttachmentsForMessage(mmsMessageId: Long): List<DatabaseAttachment>
fun getMessageBy(timestamp: Long, author: String): MessageRecord?
fun getMessageBy(threadId: Long, timestamp: Long, author: String): MessageRecord?
fun updateSentTimestamp(messageId: MessageId, newTimestamp: Long)
fun markAsResyncing(messageId: MessageId)
fun markAsSyncing(messageId: MessageId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.dependencies.ManagerScope
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.repository.ConversationRepository
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager
import java.security.SignatureException
import javax.inject.Inject
Expand Down Expand Up @@ -244,7 +243,8 @@ class ReceivedMessageHandler @Inject constructor(

val timestamp = message.timestamp ?: return null
val author = message.author ?: return null
val messageToDelete = storage.getMessageBy(timestamp, author) ?: return null
val threadId = message.threadID ?: return null
val messageToDelete = storage.getMessageBy(threadId, timestamp, author) ?: return null
val messageIdToDelete = messageToDelete.messageId
val messageType = messageToDelete.individualRecipient?.getType()

Expand Down Expand Up @@ -326,7 +326,7 @@ class ReceivedMessageHandler @Inject constructor(
Address.fromSerialized(quote.author)
}

val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author)
val messageInfo = messageDataProvider.getMessageForQuote(context.threadId, quote.id, author)
quoteMessageBody = messageInfo?.third
quoteModel = if (messageInfo != null) {
val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList()
Expand Down
18 changes: 14 additions & 4 deletions app/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ object OnionRequestAPI {
.map { it.isNotEmpty() }
.stateIn(GlobalScope, SharingStarted.Eagerly, paths.value.isNotEmpty())

private val NON_PENALIZING_STATUSES = setOf(403, 404, 406, 425)

init {
// Listen for the changes in paths and persist it to the db
GlobalScope.launch {
Expand Down Expand Up @@ -410,12 +412,20 @@ object OnionRequestAPI {
} else {
handleUnspecificError()
}
} else if (destination is Destination.Server && exception.statusCode == 400) {
Log.d("Loki","Destination server returned code ${exception.statusCode} with message: $message")
} else if(exception.statusCode in NON_PENALIZING_STATUSES){
// error codes that shouldn't penalize our path or drop snodes
// 404 is probably file server missing a file, don't rebuild path or mark a snode as bad here
Log.d("Loki","Request returned a non penalizing code ${exception.statusCode} with message: $message")
}
// we do not want to penalize the path/nodes when:
// - the exit node reached the server but the destination returned 5xx or 400
// - the exit node couldn't reach its destination with a 5xx or 400, but the destination was a community (which we can know from the server's name being in the error message)
else if (destination is Destination.Server &&
(exception.statusCode in 500..504 || exception.statusCode == 400) &&
(exception is HTTPRequestFailedAtDestinationException || exception.body?.contains(destination.host) == true)) {
Log.d("Loki","Destination server error - Non path penalizing. Request returned code ${exception.statusCode} with message: $message")
} else if (message == "Loki Server error") {
Log.d("Loki", "message was $message")
} else if (exception.statusCode == 404) {
// 404 is probably file server missing a file, don't rebuild path or mark a snode as bad here
} else { // Only drop snode/path if not receiving above two exception cases
handleUnspecificError()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -938,12 +938,6 @@ interface TextSecurePreferences {
setBooleanPreference(context, FINGERPRINT_KEY_GENERATED, true)
}

@JvmStatic
fun clearAll(context: Context) {
getDefaultSharedPreferences(context).edit().clear().commit()
}


// ----- Get / set methods for if we have already warned the user that saving attachments will allow other apps to access them -----
// Note: We only ever show the warning dialog about this ONCE - when the user accepts this fact we write true to the flag & never show again.
@JvmStatic
Expand Down Expand Up @@ -1680,8 +1674,16 @@ class AppTextSecurePreferences @Inject constructor(
return getBooleanPreference(AUTOPLAY_AUDIO_MESSAGES, false)
}

/**
* Clear all prefs and reset our observables
*/
override fun clearAll() {
getDefaultSharedPreferences(context).edit().clear().commit()
pushEnabled.update { false }
localNumberState.update { null }
postProLaunchState.update { false }
hiddenPasswordState.update { false }

getDefaultSharedPreferences(context).edit(commit = true) { clear() }
}

override fun getHidePassword() = getBooleanPreference(HIDE_PASSWORD, false)
Expand Down
19 changes: 14 additions & 5 deletions app/src/main/java/org/session/libsignal/utilities/HTTP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ object HTTP {

open class HTTPRequestFailedException(
val statusCode: Int,
val json: Map<*, *>?,
val json: Map<*, *>? = null,
val body: String? = null,
message: String = "HTTP request failed with status code $statusCode"
) : kotlin.Exception(message)
class HTTPNoNetworkException : HTTPRequestFailedException(0, null, "No network connection")
Expand Down Expand Up @@ -131,10 +132,10 @@ object HTTP {
else -> defaultConnection
}.newCall(request.build()).await().use { response ->
when (val statusCode = response.code) {
200 -> response.body!!.bytes()
in 200..299 -> response.body.bytes()
else -> {
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
throw HTTPRequestFailedException(statusCode, null)
throw HTTPRequestFailedException(statusCode, body = response.body.string())
}
}
}
Expand All @@ -143,8 +144,16 @@ object HTTP {

if (!isConnectedToNetwork()) { throw HTTPNoNetworkException() }

// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
if (exception !is HTTPRequestFailedException) {

// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
throw HTTPRequestFailedException(
statusCode = 0,
message = "HTTP request failed due to: ${exception.message}"
)
} else {
throw exception
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(),
private var albumRailAdapter: MediaRailAdapter? = null

private var windowInsetBottom = 0
private var railHeight = 0

@OptIn(ExperimentalCoroutinesApi::class)
override fun onCreate(bundle: Bundle?, ready: Boolean) {
Expand Down Expand Up @@ -172,7 +171,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(),
windowInsetBottom = insets.bottom

binding.toolbar.updatePadding(top = insets.top)
binding.mediaPreviewAlbumRailContainer.updatePadding(bottom = max(insets.bottom, binding.mediaPreviewAlbumRailContainer.paddingBottom))
binding.mediaPreviewAlbumRailContainer.updatePadding(bottom = insets.bottom)

updateControlsPosition()

Expand Down Expand Up @@ -213,12 +212,9 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(),
* Updates the media controls' position based on the rail's position
*/
private fun updateControlsPosition() {
// the ypos of the controls is either the window bottom inset, or the rail height if there is a rail
// since the rail height takes the window inset into account with its padding
val totalBottomPadding = max(
windowInsetBottom,
railHeight + resources.getDimensionPixelSize(R.dimen.medium_spacing)
)
val totalBottomPadding = windowInsetBottom +
binding.mediaPreviewAlbumRail.height+
resources.getDimensionPixelSize(R.dimen.medium_spacing)

adapter?.setControlsYPosition(totalBottomPadding)
}
Expand Down Expand Up @@ -431,7 +427,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(),
binding.mediaPreviewAlbumRailContainer.viewTreeObserver.removeOnGlobalLayoutListener(
this
)
railHeight = binding.mediaPreviewAlbumRailContainer.height

updateControlsPosition()
}
}
Expand Down
80 changes: 56 additions & 24 deletions app/src/main/java/org/thoughtcrime/securesms/ShareScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.groups.compose.MemberItem
import org.thoughtcrime.securesms.ui.SearchBar
import org.thoughtcrime.securesms.ui.components.BackAppBar
import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator
import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator
import org.thoughtcrime.securesms.ui.qaTag
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
Expand Down Expand Up @@ -66,7 +67,7 @@ fun ShareScreen(
fun ShareList(
state: ShareViewModel.UIState,
contacts: List<ConversationItem>,
hasConversations: Boolean,
hasConversations: Boolean?,
onContactItemClicked: (address: Address) -> Unit,
searchQuery: String,
onSearchQueryChanged: (String) -> Unit,
Expand Down Expand Up @@ -116,29 +117,41 @@ fun ShareList(

Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))

if (!hasConversations) {
Text(
text = stringResource(id = R.string.conversationsNone),
modifier = Modifier.padding(top = LocalDimensions.current.spacing)
.align(Alignment.CenterHorizontally),
style = LocalType.current.base.copy(color = LocalColors.current.textSecondary)
)
} else {
LazyColumn(
state = scrollState,
contentPadding = PaddingValues(bottom = paddings.calculateBottomPadding()),
) {

items(contacts) { contacts ->
// Each member's view
MemberItem(
address = contacts.address,
onClick = onContactItemClicked,
title = contacts.name,
showProBadge = contacts.showProBadge,
showAsAdmin = false,
avatarUIData = contacts.avatarUIData
)
when(hasConversations){
// null means we don't yet have a result, so show a loader
null -> {
SmallCircularProgressIndicator(
modifier = Modifier.padding(top = LocalDimensions.current.spacing)
.align(Alignment.CenterHorizontally),
)
}

false -> {
Text(
text = stringResource(id = R.string.conversationsNone),
modifier = Modifier.padding(top = LocalDimensions.current.spacing)
.align(Alignment.CenterHorizontally),
style = LocalType.current.base.copy(color = LocalColors.current.textSecondary)
)
}

true -> {
LazyColumn(
state = scrollState,
contentPadding = PaddingValues(bottom = paddings.calculateBottomPadding()),
) {

items(contacts) { contacts ->
// Each member's view
MemberItem(
address = contacts.address,
onClick = onContactItemClicked,
title = contacts.name,
showProBadge = contacts.showProBadge,
showAsAdmin = false,
avatarUIData = contacts.avatarUIData
)
}
}
}
}
Expand Down Expand Up @@ -201,6 +214,25 @@ private fun PreviewSelectEmptyContacts() {
}
}

@Preview
@Composable
private fun PreviewSelectFetchingContacts() {
val contacts = emptyList<ConversationItem>()

PreviewTheme {
ShareList(
state = ShareViewModel.UIState(false),
contacts = contacts,
hasConversations = null,
onContactItemClicked = {},
searchQuery = "",
onSearchQueryChanged = {},
onSearchQueryClear = {},
onBack = {},
)
}
}

@Preview
@Composable
private fun PreviewSelectEmptyContactsWithSearch() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ class ShareViewModel @Inject constructor(
::filterContacts
).stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

val hasAnyConversations: StateFlow<Boolean> =
val hasAnyConversations: StateFlow<Boolean?> =
conversationRepository.observeConversationList()
.map { it.isNotEmpty() }
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)

private val _uiEvents = MutableSharedFlow<ShareUIEvent>(extraBufferCapacity = 1)
val uiEvents: SharedFlow<ShareUIEvent> get() = _uiEvents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,16 @@ class DatabaseAttachmentProvider(context: Context, helper: Provider<SQLCipherOpe
attachmentDatabase.setTransferState(attachmentId, attachmentState.value)
}

override fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>? {
override fun getMessageForQuote(threadId: Long, timestamp: Long, author: Address): Triple<Long, Boolean, String>? {
val messagingDatabase = DatabaseComponent.get(context).mmsSmsDatabase()
val message = messagingDatabase.getMessageFor(timestamp, author)
val message = messagingDatabase.getMessageFor(threadId, timestamp, author)
return if (message != null) Triple(message.id, message.isMms, message.body) else null
}

override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> {
return DatabaseComponent.get(context).attachmentDatabase().getAttachmentsForMessage(mmsId)
}

override fun getMessageBodyFor(timestamp: Long, author: String): String {
val messagingDatabase = DatabaseComponent.get(context).mmsSmsDatabase()
return messagingDatabase.getMessageFor(timestamp, author)!!.body
}

override fun getAttachmentIDsFor(mmsMessageId: Long): List<Long> {
return DatabaseComponent.get(context)
.attachmentDatabase()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,6 @@ class RemoteFileDownloadWorker @AssistedInject constructor(
return File(downloadsDirectory(context), remote.sha256Hash())
}

fun cancelAll(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}

private fun uniqueWorkName(remote: RemoteFile): String {
return "download-remote-file-${remote.sha256Hash()}"
}
Expand Down
Loading