From dde82ccb2f305b578522075117a685330b1e7b57 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 13 Mar 2026 21:30:32 +0100 Subject: [PATCH 1/3] avoid race in processComposeControls during "reply privately" processComposeControls() spawns a background thread that uses the class field chatId, but by the time `setDraft(chatId, null)` is called to clear the draft, the chatId might be already the new selected chat for "reply privately" --- CHANGELOG.md | 1 + .../securesms/ConversationActivity.java | 39 +++++++------------ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784c5bbb3..86ad2e59c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix: keep original sent timestamp for resent messages * Fix: make clicking on broadcast member-added messages work always * Fix: remove notification when a message is deleted by sender +* Fix: avoid "reply privately" not quoting the selected message sometimes * Update to core 2.44.0 ## v2.43.0 diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index 7e72bd4b4..d81cec65f 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -1172,6 +1172,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity playbackViewModel.stopNonMessageAudioPlayback(); DcContext dcContext = DcHelper.getContext(context); + final int currentChatId = dcChat.getId(); Util.runOnAnyBackgroundThread(() -> { DcMsg msg = null; int recompress = 0; @@ -1181,9 +1182,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (action == ACTION_SEND_OUT) { dcContext.sendEditRequest(msgId, body); } else { - dcContext.setDraft(chatId, null); + dcContext.setDraft(currentChatId, null); } - future.set(chatId); + future.set(currentChatId); return; } @@ -1194,7 +1195,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity try { if (slideDeck.getWebxdctDraftId() != 0) { - msg = dcContext.getDraft(chatId); + msg = dcContext.getDraft(currentChatId); } else { List attachments = slideDeck.asAttachments(); for (Attachment attachment : attachments) { @@ -1239,7 +1240,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity // for WEBXDC, drafts are just sent out as is. // for preparations and other cases, cleanup draft soon. if (msg == null || msg.getType() != DcMsg.DC_MSG_WEBXDC) { - dcContext.setDraft(dcChat.getId(), null); + dcContext.setDraft(currentChatId, null); } if(msg!=null) { @@ -1255,7 +1256,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity false ); }); - doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg); + doSend = VideoRecoder.prepareVideo(ConversationActivity.this, currentChatId, msg); Util.runOnMain(() -> { try { if (progressDialog != null) progressDialog.dismiss(); @@ -1266,48 +1267,36 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } if (doSend) { - if (dcContext.sendMsg(dcChat.getId(), msg) == 0) { + if (dcContext.sendMsg(currentChatId, msg) == 0) { String lastError = dcContext.getLastError(); if (!"".equals(lastError)) { Util.runOnMain(() -> Toast.makeText(ConversationActivity.this, lastError, Toast.LENGTH_LONG).show()); } - future.set(chatId); + future.set(currentChatId); return; } } - Util.runOnMain(() -> sendComplete(dcChat.getId())); + if (currentChatId == this.chatId) { + Util.runOnMain(() -> sendComplete()); + } } } else { - dcContext.setDraft(dcChat.getId(), msg); + dcContext.setDraft(currentChatId, msg); } - future.set(chatId); + future.set(currentChatId); }); return future; } - protected void sendComplete(int chatId) { - boolean refreshFragment = (chatId != this.chatId); - this.chatId = chatId; - + protected void sendComplete() { if (fragment == null || !fragment.isVisible() || isFinishing()) { return; } fragment.setLastSeen(-1); - - if (refreshFragment) { - fragment.reload(recipient, chatId); - try { - int accId = rpc.getSelectedAccountId(); - DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId); - } catch (RpcException e) { - Log.e(TAG, "rpc.getSelectedAccountId() failed", e); - } - } - fragment.scrollToBottom(); attachmentManager.cleanup(); } From 56abaa7c2086daf888a6c4d4514c6ddc0ba86db9 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 16 Mar 2026 14:38:28 +0100 Subject: [PATCH 2/3] fix changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e68ee4c49..3fc5b60ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,9 @@ * Fix: keep original sent timestamp for resent messages * Fix: make clicking on broadcast member-added messages work always * Fix: remove notification when a message is deleted by sender -<<<<<<< adb/issue-3508 * Fix: avoid "reply privately" not quoting the selected message sometimes -* Update to core 2.44.0 -======= * Some more bug fixes and updated translations * Update to core 2.45.0 ->>>>>>> main ## v2.43.0 2026-02 From df7cc90da99ea8df8a15ff0dbdcbd6028b31a150 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 16 Mar 2026 16:28:30 +0100 Subject: [PATCH 3/3] separate code format checking to its own workflow --- .github/workflows/code-format.yml | 35 +++++++++++++++++++++++++++++++ .github/workflows/preview-apk.yml | 3 --- 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/code-format.yml diff --git a/.github/workflows/code-format.yml b/.github/workflows/code-format.yml new file mode 100644 index 000000000..ab4c42521 --- /dev/null +++ b/.github/workflows/code-format.yml @@ -0,0 +1,35 @@ +name: Code Format + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + spotless: + name: Check code format (Spotless) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 + with: + java-version: 17 + distribution: temurin + - uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Validate Gradle Wrapper + uses: gradle/actions/wrapper-validation@v4 + - name: Check formatting + run: ./gradlew spotlessCheck diff --git a/.github/workflows/preview-apk.yml b/.github/workflows/preview-apk.yml index ba1090dc2..3453f61b6 100644 --- a/.github/workflows/preview-apk.yml +++ b/.github/workflows/preview-apk.yml @@ -32,9 +32,6 @@ jobs: - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@v4 - - name: Check code format - run: ./gradlew spotlessCheck - - uses: android-actions/setup-android@v3 - uses: nttld/setup-ndk@v1 id: setup-ndk