From b65079ec202e6f6602e056f171858ef5bf71abc8 Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Tue, 16 Dec 2025 11:35:39 -0500 Subject: [PATCH] Support pinned messages in backups. --- .../chat_item_pin_message_00.binproto | Bin 0 -> 752 bytes .../chat_item_pin_message_01.binproto | Bin 0 -> 945 bytes .../chat_item_pin_message_02.binproto | Bin 0 -> 1297 bytes .../chat_item_pin_message_03.binproto | Bin 0 -> 665 bytes .../chat_item_pin_message_04.binproto | Bin 0 -> 1025 bytes .../chat_item_pin_message_05.binproto | Bin 0 -> 1208 bytes .../chat_item_pin_message_06.binproto | Bin 0 -> 756 bytes .../chat_item_pin_message_07.binproto | Bin 0 -> 951 bytes .../chat_item_pin_message_08.binproto | Bin 0 -> 1297 bytes .../chat_item_pin_message_09.binproto | Bin 0 -> 662 bytes .../chat_item_pin_message_10.binproto | Bin 0 -> 1027 bytes .../chat_item_pin_message_11.binproto | Bin 0 -> 1206 bytes .../chat_item_pin_message_12.binproto | Bin 0 -> 756 bytes .../chat_item_pin_message_13.binproto | Bin 0 -> 951 bytes .../chat_item_pin_message_14.binproto | Bin 0 -> 1299 bytes .../chat_item_pin_message_update_00.binproto | Bin 0 -> 880 bytes .../chat_item_pin_message_update_01.binproto | Bin 0 -> 880 bytes .../chat_item_pin_message_update_02.binproto | Bin 0 -> 880 bytes .../backup/v2/ArchiveImportExportTests.kt | 5 +++ .../securesms/backup/v2/ArchiveErrorCases.kt | 4 ++ .../database/MessageTableArchiveExtensions.kt | 10 ++++- .../v2/exporters/ChatItemArchiveExporter.kt | 41 +++++++++++++++++- .../v2/importer/ChatItemArchiveImporter.kt | 41 +++++++++++++++++- app/src/main/protowire/Backup.proto | 16 +++++++ gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 28 +++++------- 26 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_00.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_01.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_02.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_03.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_04.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_05.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_06.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_07.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_08.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_09.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_10.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_11.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_12.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_13.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_14.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_update_00.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_update_01.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_pin_message_update_02.binproto diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_00.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_00.binproto new file mode 100644 index 0000000000000000000000000000000000000000..a7549ad0054d54fc32285b2a44814814db12f30c GIT binary patch literal 752 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Piy4LZxp)KpT*KXq9IJ{vBD3A3`Wd;p7?rp(a}(23^|K06)3a*093(c}H@$Rs zg_i%n#Y%s*mTPk{cO<{wVE?wI?Tch~UTO|!xmfC}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Jv^9A=4o zoRAG-h?4P1^T{v^%TKiQ^9;8y_jA+D&-c-_4tI>O$gj*|P!f2%Gg5yWi?3O?gU#ly z2h!0}#~HZ}F~ZDD&rGx8aIQ>rc5-*Da@Z~9v6DZDt=ui8%EJd}w7a`jnvQO!UXE_Q zy}L@CeN>5gfrp==ZHcRKRy~(Pll0l9GuI_=`aJDyed{bSMSXFb>uTL_OO1tIxAi7W zD4Z$k|91MkmHm=8JPuY@#Q)&Ebg@5b!K8yFafR9^(}EXDd|k3|ABz%4WkHsUPiSbi z2D3rSweA^T4K0|06h6KG@}5~}-ua82CqGA7*p32J3J8ZSR@$4ouBO+6_A@%UKrwq&Hn&DjYa_g literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_02.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_02.binproto new file mode 100644 index 0000000000000000000000000000000000000000..f5f3c61905d5ffb16e6db49ac204836ffb6b3ed9 GIT binary patch literal 1297 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9UT3(9k(1-q`U|HXnE(Tjmy3miMT$Y-_4J)r4j4wAVHM)% z;tlk34RlmiQHirGGbJ@&KexcdiaoQi%Ed1rFG|KI#nV5+-p|!C z(j=s=ASfUx%hfQ#GR-ursKhL*mdjz{XGX~bS5y2>RyNB2Pc~=#62%qxdB(oFqEeSB zx3=4U+v@rCdcz{|Z8x;EE7#ppw&^>ueaVTYB=*1E|GwHtP8N{3w`%8eb|sF=f-D!G z(9moRCWDr1-7~%#1}QXddoiKwZHHIDQtJ@m(AgKKsK@53B*;!UYh!==W2F_d1fx_C zmz@$6n?N>eV?{l&bpV`}o!b z8>t$HhkNE1=Vmb|3B27IslScI*R0#YW^>mA>1e5bMy@VKWK*+hxEv%l+&8^+cZHV! zzr{*_wU%phF?S@t-eCW>rR|Gkc3x@@XSrDZMYWZ^y*q>y2ThKvPMkiw_;U*0nd%{zaw^W^6!iyW_I W{Z}NOh5zkY&E2rS`?L032}S^J*VdW< literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_03.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_03.binproto new file mode 100644 index 0000000000000000000000000000000000000000..6aff5bbf0c0c3bf48cd7e319f37e26b08f892bdc GIT binary patch literal 665 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9jMv03bk33#yxQdaFc>;`8-)D$I3Gi43od49LrtGT~BC;!G?}$;{W!EiiU?AmFh`Fo-)p j+czp8H>C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!f!NZVncKo`Vyr z6VBS$-~L!>#Vo-n6~twy#Fd$wn4YSiRgju)#o=6;= ztQsx{i4FHnFWp_C<^ON7(qFCR+FZ;X$*(uqzinyzBAK0+n!{NxmVf!zS2>C2?B{E? zxFkmYPL-N!s(Lqe<*G{kPxgiq6W2~qMDisA+?W639&B!}NV=Kx_+YWpTJK-S9&$wf SF1S5ksB42{ZuY}t5{v-8pl?wC literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_05.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_05.binproto new file mode 100644 index 0000000000000000000000000000000000000000..2f6f2f9258ad921b3fcaf1d7459529e74ce009ea GIT binary patch literal 1208 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9ERyIVCc@GBO}9TPlFdMu{^sH!(d`zaTH&iaoQi%Ed1r&taCh#|haWhA0`I zG@lH!u>3?zKhJRcaz8iS{Cpo>>u|>ii~Pzg1|@;FJ0tbCvG|&GJJ@XQdLSJwb)1pw z5F^aY^vpCX4(G~5XD4^ZDu>-t9y|Gi*vj2fsyuvvM!UOfrRnHq>gDL>+qIK|UH!rsr- zGSVcZt{^BND9hC_!ZOV?tEj{*tCq`Q;%7$516Nc0PF6O`|4%k&{1U|#_<6>@x}s8- zDYv%Ue%tE#^?Ji1@ohJ>v@6%$Qnu+kv3<#jrX=>i-T%JYNKO`zxVLKOb9N*TGeJEZ zq|mtS#e}Z69bN%TtwV%EXJ4G69-FU{AUol#js5M9l~&9^?*?(%DRDuPLRLX)I#RqL k5`$Nej+t4iUY&DAsj5%Dk8e$|k*aZcxMzNGF1Ek`0KD?2m;e9( literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_06.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_06.binproto new file mode 100644 index 0000000000000000000000000000000000000000..729b67c143b1146402d7ad6665024021a22329f8 GIT binary patch literal 756 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!f!NZVncK$2|v*tuWAH z5O_a*=amD7QHvRc__=rk{anM{iyW(pJR-B*r1}}Tx)_zXGIJBtQ}we7Qq!|)xEv%l z+&8^+cZHV!zr{*_wU%phF?S@t-eCW>rR|Gkc3x@@XSrDZ-aU&kJDME)+gJzuD6gJf>@!($SR0EUVNTmS$7 literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_07.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_07.binproto new file mode 100644 index 0000000000000000000000000000000000000000..defcf467cb6922c390ad9e735afc2f99ca03b256 GIT binary patch literal 951 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!gxNru zEcCjqH(^5IOi};0)90=1m%QO|u(~4t2k)he{ZR`h9W03})IOOOyinrnl7;(NlsGC2 zvRr&ZL$fuQ4O*^s&-iL+!4#zM>HU}Y%tG_dU+g^jIm#l(YgzvliD%(|dscHd?C<`p tJy(KJ%7jZni8HY@B{N??x4_upfq=&%!65GZY~QGW+^q7#5HD>02LQZ!M%MrU literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_08.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_08.binproto new file mode 100644 index 0000000000000000000000000000000000000000..6dc43d4ae65d67dee9dbc59cbc6023d568faedb3 GIT binary patch literal 1297 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9lmiQHirGGbJ@&KexcdiaoQi%Ed1rFG|KI#nV5+-p|!C z(j=s=ASfUx%hfQ#GR-ursKhL*mdjz{XGX~bS5y2>RyNB2Pc~=#62%qxdB(oFqEeSB zx3=4U+v@rCdcz{|Z8x;EE7#ppw&^>ueaVTYB=*1E|GwHtP8N{3w`%8eb|sF=f-D!G z(9moRCWDr1-7~%#1}QXddoiKwZHHIDQtJ@m(AgKKsK@53B*;!UYh!==W2F_d1fx_C zmz@$6n?N>eV?{l&bpV`}o!b z8>t$HhkNE1=Vmb|3B27IslScI*R0#YW^>mA>1e5bMy@VKWK*+hxEv%l+&8^+cZHV! zzr{*_wU%phF?S@t-eCW>rR|Gkc3x@@XSrDZMYWZ^y*q>y2ThKvPMkiw_;U*0nd%{zaw^W^6!iyW_I W{Z}NOh5zkY&E2rS`?L032}S_t8P>!A literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_09.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_09.binproto new file mode 100644 index 0000000000000000000000000000000000000000..540b319162d115b522c07891f5c57a43f39842c4 GIT binary patch literal 662 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9vpi&-1R^@8UQ%b*0TTr literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_10.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_10.binproto new file mode 100644 index 0000000000000000000000000000000000000000..9ef8daab3919c1efe54d597cd9d4218241a0315d GIT binary patch literal 1027 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!gn8|dd6?q1|rRpb$w?IyL2k!vHP5@%UvN@~7-Zh?svduCyk zi(f!ql#EY`r+ zY|i*4iYxH*jD2-Qr7lx$ZMXfl)${B1hDGArZfI#&uDhjd(|2O~k`qly?0>ueeYKIC zEFf`j)z0VaN*t91SuQ@Iq1hTt1})dRXM8mbQfS=vVnWy34zGZv)*-^7voB6jkIh#} zkezVW#{TxlN-JgwMyVh!J0-5n+{E-${j7r2bSn<$%0y=;cgHG+S>hfiWP=!h9`Fj% zF*8fmt8=a>RrSgD@vR9qQZ)___slQO&0& zv&AJb@^`A#R8!Tvu`5?q>VL8~l$f}7f+CVH8Q{MBANOE$dqvXCoW}=?mDYOyI`)tw U@^``Q`9fVABy+PL9+O}M0HJYjjsO4v literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_11.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_11.binproto new file mode 100644 index 0000000000000000000000000000000000000000..654866d38db2eb0d708a580a245d8789ab849041 GIT binary patch literal 1206 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9^rVOi$G>$V<0k&n&ER@e9avm?iFULN z2s1N1GtG*_xiZn&$=$KaVYigWPW~Xaa<`N!4?(SM?I=Y#9IlB4w?kaWmQ6=UD z9)56VM3tlMkb;-hgEJ_@e1z9dWp`qCt%myvjx@UYf zv|tKS`1JnEduE||=P!1i{2XPGpv0M2nv$8X zpIcz;@Ib(0kzf#aeztE^KyFrfVTc!2|8HaD+6eT2S!PNqNS_HjOo1Lw@$`?d_j9$3 zGzqCI2nq$>XYx|TN7-gY8)Q!nO~fXEieF)HK(rt literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_12.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_12.binproto new file mode 100644 index 0000000000000000000000000000000000000000..16d2304088c49f9fd2e78a4c0df227a3b94218ab GIT binary patch literal 756 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!f!NZVncK$2|v*tuXLn z5coQM=amD7QHvRc__=rk{anM{iyW(pJR-B*r1}}Tx)_zXGIJBtQ}we7Qq!|)xEv%l z+&8^+cZHV!zr{*_wU%phF?S@t-eCW>rR|Gkc3x@@XSrDZ-aU&kJDME)+gJzuD6gJf>@!($SR0IC`WjsO4v literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_13.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_13.binproto new file mode 100644 index 0000000000000000000000000000000000000000..7c32a441291f72b85b4807945258b4e46a6a4046 GIT binary patch literal 951 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9Q!gxNru zEcCjqH(^5IOi};0)90=1m%QO|u(~4t2k)he{ZR`h9W03})IOOOyinrnl7;(NlsGC2 zvRr&ZL$fuQ4O*^s&-iL+!4#zM>HU}Y%tG_dU+g^jIm#l(YgzvliD%(|dscHd?C<`p tJy(KJ%7jZni8HY@B{N??x4_upfq=&%!65GZY~QGW+^q7#5HD>02LSqnM*#o; literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_14.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_14.binproto new file mode 100644 index 0000000000000000000000000000000000000000..1030a76c57dffa7718123fdebfc6ae45d13bddc1 GIT binary patch literal 1299 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9UL}t55ZDZux$f(3wmYI^8ub*3BV#S_WSmojukQXK6lj7+gVejW^ z8EFzyR}d5sl;vs|VVP!{Ra9b@Rmeu?4={5)e{T~Vpa zlv~?vzisvWdc9$h__iBb+Lh~WDckg&*uLaMQxf~%?tfoxBqs|<+*`HtIlB@^WkHsU zPiSbi29rU{weA^T4TBUKx4oFq^|r$+V5xP8aOmudQ`BSgRT5+;oVBsP{jt)DS%Oh2 zh|5liD>FASJyk!eAT`~J!?`li*~#6p%3+qc#|haW2A~JLf^^KxQuXSbD@s*;@_l@3 zf{j#-!^1uEi*vIWlmy=HjMU%8;%nCJV6(aFfpoM~KOc``ZT!(-W z4ly%5GYu)=c1wBent%veQ}%XYTa;4jfGyf^(IUxoGI%6cKW=P{gO944pvvh z|KPoJu|I0Tq=O}Kh1w_6f)`4BU9xZ=3sT51Lqo=bDM;bd`!DaAh31{V*m?4Eltqr$ Xvi>U)&%*!qtmbam-~Cy8t^^|h|KryJ literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_update_00.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_update_00.binproto new file mode 100644 index 0000000000000000000000000000000000000000..cee46bfb8d6afe0e013a8f54c632581ed0b5eaf1 GIT binary patch literal 880 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9#dq16`tfFDLi>dj7wsk3M>Jk}pc)P-f+}RB=afa(sS1`^^GV G0Y(71-t6oE literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_pin_message_update_01.binproto b/app/src/androidTest/assets/backupTests/chat_item_pin_message_update_01.binproto new file mode 100644 index 0000000000000000000000000000000000000000..a432ad73c6044a2dda0d6be35ecf0ceca72e3d33 GIT binary patch literal 880 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9#dq16`tfFDLi>dj7wsk3M>Jk}pc)P-fC}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zg1L)PsGX66MW~4pD9#dq16`tfFDLi>dj7wsk3M>Jk}pc)P-f+}RB=afa(sS1`^^GV G0agIH;_UDM literal 0 HcmV?d00001 diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt index d61388b94a..fb831a9e19 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt @@ -216,6 +216,11 @@ class ArchiveImportExportTests { runTests { it.startsWith("chat_item_poll_") } } +// @Test + fun chatItemPinMessage() { + runTests { it.startsWith("chat_item_pin_message_") } + } + // @Test fun notificationProfiles() { runTests { it.startsWith("notification_profile_") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt index c1d2200855..6b16d9cd34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt @@ -139,6 +139,10 @@ object ExportSkips { return log(sentTimestamp, "Poll was not in a group chat.") } + fun pinMessageIsInvalid(sentTimestamp: Long): String { + return log(sentTimestamp, "Pin message update was invalid.") + } + fun individualChatUpdateInWrongTypeOfChat(sentTimestamp: Long): String { return log(sentTimestamp, "A chat update that only makes sense for individual chats was found in a different kind of chat.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt index 46270a3754..4e8a7e14da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt @@ -65,7 +65,10 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self ${MessageTable.MISMATCHED_IDENTITIES}, ${MessageTable.TYPE}, ${MessageTable.MESSAGE_EXTRAS}, - ${MessageTable.VIEW_ONCE} + ${MessageTable.VIEW_ONCE}, + ${MessageTable.PINNED_UNTIL}, + ${MessageTable.PINNING_MESSAGE_ID}, + ${MessageTable.PINNED_AT} ) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 """.trimMargin() @@ -155,7 +158,10 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self MessageTable.TYPE, MessageTable.MESSAGE_EXTRAS, MessageTable.VIEW_ONCE, - PARENT_STORY_ID + PARENT_STORY_ID, + MessageTable.PINNED_UNTIL, + MessageTable.PINNING_MESSAGE_ID, + MessageTable.PINNED_AT ) .from("${MessageTable.TABLE_NAME} INDEXED BY $dateReceivedIndex") .where("$STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND ($EXPIRES_IN == 0 OR $EXPIRES_IN > ${1.days.inWholeMilliseconds}) AND $DATE_RECEIVED >= $lastSeenReceivedTime $cutoffQuery") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index fc1d0a827e..d86270239c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -9,6 +9,7 @@ import android.database.Cursor import okio.ByteString.Companion.toByteString import org.json.JSONArray import org.json.JSONException +import org.signal.core.models.ServiceId import org.signal.core.util.Base64 import org.signal.core.util.EventTimer import org.signal.core.util.Hex @@ -53,6 +54,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall import org.thoughtcrime.securesms.backup.v2.proto.LearnedProfileChatUpdate import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment import org.thoughtcrime.securesms.backup.v2.proto.PaymentNotification +import org.thoughtcrime.securesms.backup.v2.proto.PinMessageUpdate import org.thoughtcrime.securesms.backup.v2.proto.Poll import org.thoughtcrime.securesms.backup.v2.proto.PollTerminateUpdate import org.thoughtcrime.securesms.backup.v2.proto.ProfileChangeChatUpdate @@ -413,6 +415,16 @@ class ChatItemArchiveExporter( transformTimer.emit("poll") } + MessageTypes.isPinnedMessageUpdate(record.type) -> { + val pinMessageUpdate = record.toRemotePinMessageUpdate(exportState) + if (pinMessageUpdate == null) { + Log.w(TAG, ExportSkips.pinMessageIsInvalid(record.dateSent)) + continue + } + builder.updateMessage = ChatUpdateMessage(pinMessage = pinMessageUpdate) + transformTimer.emit("pin-message") + } + else -> { val attachments = extraData.attachmentsById[record.id] val sticker = attachments?.firstOrNull { dbAttachment -> dbAttachment.isSticker } @@ -595,6 +607,7 @@ private fun BackupMessageRecord.toBasicChatItemBuilder(selfRecipientId: Recipien expiresInMs = record.expiresIn.takeIf { it > 0 } revisions = emptyList() sms = record.type.isSmsType() + pinDetails = record.toPinDetails() when (direction) { Direction.DIRECTIONLESS -> { directionless = ChatItem.DirectionlessMessageDetails() @@ -847,6 +860,27 @@ private fun BackupMessageRecord.toRemotePollTerminateUpdate(): PollTerminateUpda ) } +private fun BackupMessageRecord.toPinDetails(): ChatItem.PinDetails? { + return if (this.pinnedAt == 0L || this.pinnedUntil == 0L) { + null + } else { + ChatItem.PinDetails( + pinnedAtTimestamp = this.pinnedAt, + pinExpiresAtTimestamp = this.pinnedUntil.takeIf { it != MessageTable.PIN_FOREVER }, + pinNeverExpires = (this.pinnedUntil == MessageTable.PIN_FOREVER).takeIf { it } + ) + } +} + +private fun BackupMessageRecord.toRemotePinMessageUpdate(exportState: ExportState): PinMessageUpdate? { + val pinMessage = this.messageExtras?.pinnedMessage ?: return null + val authorId = exportState.aciToRecipientId[ServiceId.ACI.parseOrNull(pinMessage.targetAuthorAci).toString()] ?: return null + return PinMessageUpdate( + targetSentTimestamp = pinMessage.targetTimestamp, + authorId = authorId + ) +} + private fun BackupMessageRecord.toRemoteSharedContact(attachments: List?): Contact? { if (this.sharedContacts.isNullOrEmpty()) { return null @@ -1591,7 +1625,8 @@ private fun Long.isDirectionlessType(): Boolean { MessageTypes.isGroupUpdate(this) || MessageTypes.isGroupV1MigrationEvent(this) || MessageTypes.isGroupQuit(this) || - MessageTypes.isPollTerminate(this) + MessageTypes.isPollTerminate(this) || + MessageTypes.isPinnedMessageUpdate(this) } private fun Long.isIdentityVerifyType(): Boolean { @@ -1776,6 +1811,8 @@ private fun Cursor.toBackupMessageRecord(pastIds: Set, backupStartTime: Lo messageExtras = messageExtras.parseMessageExtras(), viewOnce = this.requireBoolean(MessageTable.VIEW_ONCE), parentStoryId = this.requireLong(MessageTable.PARENT_STORY_ID), + pinnedAt = this.requireLong(MessageTable.PINNED_AT), + pinnedUntil = this.requireLong(MessageTable.PINNED_UNTIL), messageExtrasSize = messageExtras?.size ?: 0 ) } @@ -1816,6 +1853,8 @@ private class BackupMessageRecord( val baseType: Long, val messageExtras: MessageExtras?, val viewOnce: Boolean, + val pinnedAt: Long, + val pinnedUntil: Long, private val messageExtrasSize: Int ) { val estimatedSizeInBytes: Int = (body?.length ?: 0) + diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index 14a51c0300..aa04260860 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescrip import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone +import org.thoughtcrime.securesms.database.model.databaseprotos.PinnedMessage import org.thoughtcrime.securesms.database.model.databaseprotos.PollTerminate import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent @@ -137,7 +138,10 @@ class ChatItemArchiveImporter( MessageTable.LATEST_REVISION_ID, MessageTable.REVISION_NUMBER, MessageTable.PARENT_STORY_ID, - MessageTable.NOTIFIED + MessageTable.NOTIFIED, + MessageTable.PINNED_UNTIL, + MessageTable.PINNING_MESSAGE_ID, + MessageTable.PINNED_AT ) private val REACTION_COLUMNS = arrayOf( @@ -343,6 +347,32 @@ class ChatItemArchiveImporter( SignalDatabase.polls.endPoll(pollId = pollId, endingMessageId = endPollMessageId) } } + } else if (this.updateMessage.pinMessage != null) { + followUps += { pinUpdateMessageId -> + val targetAuthorId = importState.remoteToLocalRecipientId[updateMessage.pinMessage.authorId] + if (targetAuthorId != null) { + val pinnedMessageId = SignalDatabase.messages.getMessageFor(updateMessage.pinMessage.targetSentTimestamp, targetAuthorId)?.id ?: -1 + val messageExtras = MessageExtras( + pinnedMessage = PinnedMessage( + pinnedMessageId = pinnedMessageId, + targetAuthorAci = recipients.getRecord(targetAuthorId).aci!!.toByteString(), + targetTimestamp = updateMessage.pinMessage.targetSentTimestamp + ) + ) + + db.update(MessageTable.TABLE_NAME) + .values(MessageTable.MESSAGE_EXTRAS to messageExtras.encode()) + .where("${MessageTable.ID} = ?", pinUpdateMessageId) + .run() + + if (pinnedMessageId != -1L) { + db.update(MessageTable.TABLE_NAME) + .values(MessageTable.PINNING_MESSAGE_ID to pinUpdateMessageId) + .where("${MessageTable.ID} = ?", pinnedMessageId) + .run() + } + } + } } } @@ -645,6 +675,12 @@ class ChatItemArchiveImporter( contentValues.put(MessageTable.REMOTE_DELETED, 0) contentValues.put(MessageTable.PARENT_STORY_ID, 0) + if (this.pinDetails != null) { + val pinnedUntil = if (this.pinDetails.pinNeverExpires == true) MessageTable.PIN_FOREVER else this.pinDetails.pinExpiresAtTimestamp + contentValues.put(MessageTable.PINNED_UNTIL, pinnedUntil ?: 0) + contentValues.put(MessageTable.PINNED_AT, this.pinDetails.pinnedAtTimestamp) + } + when { this.standardMessage != null -> contentValues.addStandardMessage(this.standardMessage) this.remoteDeletedMessage != null -> contentValues.put(MessageTable.REMOTE_DELETED, 1) @@ -846,6 +882,9 @@ class ChatItemArchiveImporter( updateMessage.pollTerminate != null -> { typeFlags = MessageTypes.SPECIAL_TYPE_POLL_TERMINATE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv()) } + updateMessage.pinMessage != null -> { + typeFlags = MessageTypes.SPECIAL_TYPE_PINNED_MESSAGE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv()) + } updateMessage.sessionSwitchover != null -> { typeFlags = MessageTypes.SESSION_SWITCHOVER_TYPE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv()) val sessionSwitchoverDetails = SessionSwitchoverEvent(e164 = updateMessage.sessionSwitchover.e164.toString()).encode() diff --git a/app/src/main/protowire/Backup.proto b/app/src/main/protowire/Backup.proto index 15abe2f367..16f6f134ed 100644 --- a/app/src/main/protowire/Backup.proto +++ b/app/src/main/protowire/Backup.proto @@ -460,6 +460,14 @@ message ChatItem { message DirectionlessMessageDetails { } + message PinDetails { + uint64 pinnedAtTimestamp = 1; + oneof pinExpiry { + uint64 pinExpiresAtTimestamp = 2; // timestamp when the pin should expire + bool pinNeverExpires = 3; + } + } + uint64 chatId = 1; // conversation id uint64 authorId = 2; // recipient id uint64 dateSent = 3; @@ -488,6 +496,8 @@ message ChatItem { DirectStoryReplyMessage directStoryReplyMessage = 19; // group story reply messages are not backed up Poll poll = 20; } + + PinDetails pinDetails = 21; // only set if message is pinned } message SendStatus { @@ -898,6 +908,7 @@ message ChatUpdateMessage { GroupCall groupCall = 8; LearnedProfileChatUpdate learnedProfileChange = 9; PollTerminateUpdate pollTerminate = 10; + PinMessageUpdate pinMessage = 11; } } @@ -1268,6 +1279,11 @@ message PollTerminateUpdate { string question = 2; // Between 1-100 characters } +message PinMessageUpdate { + uint64 targetSentTimestamp = 1; + uint64 authorId = 2; // recipient id +} + message StickerPack { bytes packId = 1; bytes packKey = 2; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7d02e093d..88c8e82eeb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ androidx-window = "1.3.0" glide = "4.15.1" gradle = "8.9.0" kotlin = "2.2.20" -libsignal-client = "0.86.6" +libsignal-client = "0.86.8" mp4parser = "1.9.39" android-gradle-plugin = "8.10.1" accompanist = "0.28.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e3431d7adb..849e788fa3 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -15400,28 +15400,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - + + + - - - - + + - - - - - + + + - - - - + +