From c2e5f4e80afb415c217d5e10e3e054898695c6e0 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Fri, 26 Jun 2015 20:14:51 -0700 Subject: [PATCH] Add transfer indicators for attachments Closes #3498 // FREEBIE --- build.gradle | 10 +- .../ic_missing_thumbnail_picture.png | Bin 991 -> 2928 bytes res/drawable-hdpi/stat_sys_download_anim0.png | Bin 369 -> 0 bytes res/drawable-hdpi/stat_sys_download_anim1.png | Bin 329 -> 0 bytes res/drawable-hdpi/stat_sys_download_anim2.png | Bin 348 -> 0 bytes res/drawable-hdpi/stat_sys_download_anim3.png | Bin 348 -> 0 bytes res/drawable-hdpi/stat_sys_download_anim4.png | Bin 319 -> 0 bytes res/drawable-hdpi/stat_sys_download_anim5.png | Bin 354 -> 0 bytes .../ic_missing_thumbnail_picture.png | Bin 684 -> 1877 bytes res/drawable-mdpi/stat_sys_download_anim0.png | Bin 286 -> 0 bytes res/drawable-mdpi/stat_sys_download_anim1.png | Bin 263 -> 0 bytes res/drawable-mdpi/stat_sys_download_anim2.png | Bin 281 -> 0 bytes res/drawable-mdpi/stat_sys_download_anim3.png | Bin 275 -> 0 bytes res/drawable-mdpi/stat_sys_download_anim4.png | Bin 253 -> 0 bytes res/drawable-mdpi/stat_sys_download_anim5.png | Bin 276 -> 0 bytes .../ic_missing_thumbnail_picture.png | Bin 1333 -> 3953 bytes .../stat_sys_download_anim0.png | Bin 464 -> 0 bytes .../stat_sys_download_anim1.png | Bin 412 -> 0 bytes .../stat_sys_download_anim2.png | Bin 433 -> 0 bytes .../stat_sys_download_anim3.png | Bin 419 -> 0 bytes .../stat_sys_download_anim4.png | Bin 397 -> 0 bytes .../stat_sys_download_anim5.png | Bin 457 -> 0 bytes .../ic_missing_thumbnail_picture.png | Bin 1828 -> 2297 bytes res/drawable/progress_background.xml | 5 + res/drawable/stat_sys_download.xml | 30 --- res/layout/conversation_activity.xml | 4 +- res/layout/media_overview_item.xml | 8 +- res/layout/thumbnail_view.xml | 41 ++-- res/values/attrs.xml | 6 - .../securesms/BaseActionBarActivity.java | 11 +- .../securesms/ConversationItem.java | 3 +- .../components/ForegroundImageView.java | 232 ------------------ .../components/SquareFrameLayout.java | 35 +++ .../securesms/components/ThumbnailView.java | 119 ++++++--- .../securesms/database/MmsDatabase.java | 6 + .../securesms/database/PartDatabase.java | 86 ++++--- .../securesms/jobs/AttachmentDownloadJob.java | 17 +- .../securesms/jobs/AvatarDownloadJob.java | 2 +- .../jobs/MultiDeviceContactUpdateJob.java | 7 +- .../jobs/MultiDeviceGroupUpdateJob.java | 5 +- .../securesms/jobs/PartProgressEvent.java | 15 ++ .../securesms/jobs/PushMediaSendJob.java | 10 + .../securesms/jobs/PushSendJob.java | 15 +- .../securesms/mms/ImageSlide.java | 2 +- .../securesms/mms/IncomingMediaMessage.java | 2 +- .../securesms/mms/OutgoingMediaMessage.java | 1 - src/org/thoughtcrime/securesms/mms/Slide.java | 6 +- .../thoughtcrime/securesms/mms/SlideDeck.java | 3 +- .../securesms/mms/ThumbnailTransform.java | 48 ---- .../com/google/android/mms/pdu/PduBody.java | 2 +- .../com/google/android/mms/pdu/PduPart.java | 10 +- 51 files changed, 301 insertions(+), 440 deletions(-) delete mode 100644 res/drawable-hdpi/stat_sys_download_anim0.png delete mode 100644 res/drawable-hdpi/stat_sys_download_anim1.png delete mode 100644 res/drawable-hdpi/stat_sys_download_anim2.png delete mode 100644 res/drawable-hdpi/stat_sys_download_anim3.png delete mode 100644 res/drawable-hdpi/stat_sys_download_anim4.png delete mode 100644 res/drawable-hdpi/stat_sys_download_anim5.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim0.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim1.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim2.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim3.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim4.png delete mode 100644 res/drawable-mdpi/stat_sys_download_anim5.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim0.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim1.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim2.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim3.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim4.png delete mode 100644 res/drawable-xhdpi/stat_sys_download_anim5.png create mode 100644 res/drawable/progress_background.xml delete mode 100644 res/drawable/stat_sys_download.xml delete mode 100644 src/org/thoughtcrime/securesms/components/ForegroundImageView.java create mode 100644 src/org/thoughtcrime/securesms/components/SquareFrameLayout.java create mode 100644 src/org/thoughtcrime/securesms/jobs/PartProgressEvent.java delete mode 100644 src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java diff --git a/build.gradle b/build.gradle index b3072e0a4b..351d21faf2 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,8 @@ dependencies { compile 'com.github.chrisbanes.photoview:library:1.2.3' compile 'com.github.bumptech.glide:glide:3.6.0' compile 'com.makeramen:roundedimageview:2.1.0' + compile 'com.pnikosis:materialish-progress:1.5' + compile 'de.greenrobot:eventbus:2.4.0' compile ('com.afollestad:material-dialogs:0.7.3.1') { exclude module: 'appcompat-v7' exclude module: 'recyclerview-v7' @@ -72,7 +74,7 @@ dependencies { compile 'org.whispersystems:jobmanager:0.11.0' compile 'org.whispersystems:libpastelog:1.0.6' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - compile 'org.whispersystems:textsecure-android:1.6.0' + compile 'org.whispersystems:textsecure-android:1.6.1' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' @@ -104,6 +106,8 @@ dependencyVerification { 'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc', 'com.github.bumptech.glide:glide:adf657e6bddccb168a29e18ab0954043af46a9b5c736d8c3193c9783fd83d69e', 'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1', + 'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54', + 'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968', 'com.afollestad:material-dialogs:c17205f0d300baa307599c428a5473a6659684c94a5f68ae3c2b84b5e4741172', 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c', 'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177', @@ -119,11 +123,11 @@ dependencyVerification { 'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d', 'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', - 'org.whispersystems:textsecure-android:b5786690a2603ca78eed8a4f829737c41e2b5099695ce02bd44d0a9af3392318', + 'org.whispersystems:textsecure-android:843d4483e9c3b3414373ddd70df19895b3ee7ef559eeb15e60926e1b07fcecf3', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', - 'org.whispersystems:textsecure-java:dd32ab5fbb232116e7e533a78dce7b8be168bf561c5774772406aea54a677c0a', + 'org.whispersystems:textsecure-java:f161c5d5be5a0ba52ede273692ef17982b2af270c6af5c3666bc2adb289a3f61', 'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0', 'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab', 'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74', diff --git a/res/drawable-hdpi/ic_missing_thumbnail_picture.png b/res/drawable-hdpi/ic_missing_thumbnail_picture.png index 05cbe34d842ef8f3067c1feb5ceb64d53b977c30..b79b403295098b036feb8dcd21572681fc0ac8ba 100644 GIT binary patch literal 2928 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 diff --git a/res/drawable-hdpi/stat_sys_download_anim0.png b/res/drawable-hdpi/stat_sys_download_anim0.png deleted file mode 100644 index 4dca0ecd84f2877e4b595dcca1c539c58fa82582..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmV-%0gnEOP)1TqbIooWs}L;>3 zeWjhW>GgRod^E4}vtu37mS?QOwXo1{3cV0cEh?P*vT37oM|GAv`rG^j+Boo?<+a{9 P00000NkvXXu0mjfCu63T diff --git a/res/drawable-hdpi/stat_sys_download_anim1.png b/res/drawable-hdpi/stat_sys_download_anim1.png deleted file mode 100644 index cf1ac1db8780de9adbdda2f89ab49076f13a282a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 329 zcmV-P0k-~$P)kdI2|e7cjBkj~LQz4W~{Fys_egrMA)=Vgizah#$T;TLS4xAp^X#=94dN%>u#>9s!q(xkqKJ zH${ zQPNW5w0<2LErtJWGA1NJZv){nw( bbcFl@nJqswpD1kh00000NkvXXu0mjfRRM=6 diff --git a/res/drawable-hdpi/stat_sys_download_anim2.png b/res/drawable-hdpi/stat_sys_download_anim2.png deleted file mode 100644 index 449d2b127039ec6d324145c8e339a18fff6346c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 348 zcmV-i0i*tjP)mMVJ1^TkqlZJq7?M(=pO`4L|GKiudA z&j+8vYbS~_amAcRp2S>RXQ`q~964j*w6_oqs3>rv(%wom!%~RZLDWcxBVqIl!-i-G zIy0)C5s@BuSaQayB%#{lRUHGO(kExS`rDgz2t66bzneZIO6;1*ijoT;Yop57R2HY6 z$7_P>m|LdUhNj@GohGQdK_yCY8Z@b@2_-ovL6fSwq2iu?x+bW4TnlXu*@$0Y@28CoE?7H`@I>+B&~glu!UgvC>fh0000e{7>9`U@h5MEuo-#*b!lpuP>^TDU^ z>i^+H4|raA6J9-0l!-HD+;S&o>e{7>PH~isg;P%#F`%WwiB`R%sK=f#`h_7=ER~q5 zqn$y~f5h~;!lDZ)l7y&_mvIb;nnIdpd8O4j1P#OZw<#D=W7kAh)SLiW7gf2YvN+{9 zXyR4-T+(1yG!;id(@m8Jm8ivG(4?v+(v%zoO{(gGmTQWjNmunb7pU)Mi<4oxWaDQn uDicPS?>}QVIO>QvVKF~?qVzY~?tB5Tkz)V>3fPqZ0000!W#1qHe7JOMI;RYh8$77mk;KZ&{p#KsLRX1GgXKFqD7d z$gSE_X9hg8<&6)q)ke1g$w7+1j)*qoLq)FyzS)VO4f*6RI*1OUF+}eKe)ttZd#V@B zmn~mk8SE=3?h0WWg;p1(mpG!&B@=TQ^G8vmz-=3OX7*HKkSC$6LoGNy(yW zO-mg=v^Mc~%Q=wsQ58g$l{)Iq(lw?!;)c#tQPXhRPh+awRH79prWvZLXel^0%}~`f z58SaFt})euD-rj2IV8jN>K+MP;3+7RTCv9M6Rmi}(vkI`U*}E78!`o;@jxp;ARYq5@QG;aO}{= zgpg1Qk%j-ySx%ZSe~�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 diff --git a/res/drawable-mdpi/stat_sys_download_anim1.png b/res/drawable-mdpi/stat_sys_download_anim1.png deleted file mode 100644 index 1c445d7d34b0f869b158109697cf871b01133c6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263 zcmV+i0r>ujP)sbcz4~ N002ovPDHLkV1iP9Y1se( diff --git a/res/drawable-mdpi/stat_sys_download_anim2.png b/res/drawable-mdpi/stat_sys_download_anim2.png deleted file mode 100644 index 8efbe910b43368946879ecc393de76b0a155eb57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 281 zcmV+!0p|XRP)f$IVDMf}@!%c8 zV+iwO#0`&Z#2PRJvMpZt;9WeRIYxNa@|(Cv^S^xINz1Qd6C)UL&KWYpvHq4N zFF3+sCDrOa#2kHyqw(G$pj?r}tPj#o)T0gc=K0xW$zrs7n4f2!($muY+FP9eUmQ@7 fk&iTGi@)U)OZg-HE=3~B00000NkvXXu0mjfEB$(w diff --git a/res/drawable-mdpi/stat_sys_download_anim3.png b/res/drawable-mdpi/stat_sys_download_anim3.png deleted file mode 100644 index 8698c6acf3bfbae1b6d918bb0254bf76f2495085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmV+u0qp*XP)U=03v@BIgS{n|lx6i@d}OYg*}JZ7Xf!$(z^kOytZj1oVsy z!zGF!Q{miH6W+^xTxsZ3Tenc`3riII!aBj zkNnY&BxAMi&v?FarEHC7N?Y>F{Gy##*S1~Vh3qKWXI_|@nEuJFq@$%nXHHZL7}&6! Zz5uLdEJkmn_E`V`002ovPDHLkV1j~dbm0I1 diff --git a/res/drawable-mdpi/stat_sys_download_anim4.png b/res/drawable-mdpi/stat_sys_download_anim4.png deleted file mode 100644 index 174836814ef36935517d0458c2588cdad3303978..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmVhz!TeSuR;7nY6@wixg#t))v006?eGFe1zM3LOSS9c}lU2Ew3606O#r@gsZa zu<&N(#TjO}@oeRfMfLdrPtqhm&m=txghJp!`p~4wC|#pd6MEcBqfs-t;_K33z^#0)%wp&_<|)jdvpifCKv`q1OD> a?BYMc{6AZ7RL=1L000029f^h=dYqzz~#xgx=c;d+$Hz$CyI;MXu+4s#}!m&I@;Yow3p+N^ru9C z@CiZ3)aswx*#9-2I`mX^IKcW@D8kQv z5IVQagXt%a)F~cgEeRMVw*S47rq1rR>h1n^!zHOIj6n9Bze)gX-nZW0E!WM7Kl|tK zRm;RKHwoYyXl`xyAg~c>UqNVHR|`v0R+L*Q(^}+KMVN+0L|T$x9MAdIt1$tKMU%_wMkPZ@N_z9i zc)M(oW-{kliYN!(0>}%D+~ht>abU;G6hHRDYO0i+NHGGQih2Xjl8sLW*(v8$cXsfW zd|l7f7VcmdsxX|4cexlG&vI$4C!`9`jrl7CSrWQ`-4kd=_qV%onc~P z+MGb%8%b;^E(TAIjmbW{$t_UBi=|SZ7{RFyG=&&&N&0?%AHBW3$G?6}tf{TF+YQcs z5UL7i?I4j{uj(YeK?VDGdmHVfR;(S9@_O{8JZWQUie?`FMs%chVR91e#vVm^2794qQv zbfWu!5D2s>V%d3rC-t<{CYcx0L}w|~5_Cj)ul zh{BWVxwR&`;gf}MBs8o?-C51MIY}rw9KQ7Ni?g|2$q)pb{n|ki4$g9bluRRt^YSux zJ%fT8$Sr1!#FgH@zW7E=dlUm8&s8{U3*K{s!Q1DyzjuoF=Bj$s`$_yd>hHa1auG5v z1x(n?dJjOdlbhzYw`*%^YO(@}9@6G-J3Bj@Hr|^y;WS&f{8niS9S&%+<}zlb1vV88 z?bYp2$DZ)L8@OaVnfX)-?n9la5kC6jn({g!Dakr6mbg<~GC%J=6u8{bpt7{QJVGYt zIJ>7wLr+&Xd%}iadkTwlCk4uT=tDLY4lfHAa7kM5@ zdb>$PgItk+5H>(3$&f+$J>Rb9|d%&-yG1Alm<`tV zjy_CxJN3^VafuaS0mmc3Ouojl-FaeBSa`BjR;PBQR6$WHe8A+ zNqXx;eqF8i)yo%z(%Z}<=`GqUxixI6=z-tQ8F$)^e(NlURn4py-6lar#8dq(1_VV` zrV-t$J0{XCTbF!W!xY_v?64Ut=1nD=jgCo!$Af0dKaGWCcz@sp%;V_G25Ixp#I1VO z#pbOaXx7L|MHF6OypkKCM+TF~Hoc|K7nM9h)pf_uw(1%aur8&VH>TH?-XK40Qjuti zMu$G}Du;N-{6;Q{q_JNH(v47;WRn$`_o%($jvX zq#?F$)$fF5zNL;3;=`99QeO6(_<~7~m4%^wxmDR{lvBe-j~0h6ZH6d?dVP;zl;%27 zMCerAAR8dWG(`na4CZUQUWI$97}M?Wg9r`MXDnM0n|1CEN_}?}fCj7r0*5a;a*dwD zt)f!8BocqVpsN^hflK-81Ax41PaW=$QF^s%VIDuHr`&CJ^6xe9R)X|5Hfzgxk{+i* z^q*yloe}PJyDy2nLG12k#_75|o^IIEkK{`60D#YzR(||=I7HUipRejb;GZ7);%h@P z>2V)IIPcvU7YLSD;(y(HK`ryI}n z(`0fpGAedhkF2~D8EH|kie(m^zUuy<^t(xy-o{4`oT(O=&$E=HD9-@J^M$f3G-Xeg zs(mARury3ffR_4q2;+i{(HKHCVauhrH5<_kdOPmrS8W=zGZX%hUU1K2?%=2_q`p^TW13%>Dw8{Gf z;@9-*%4?~$GMLQ8Pp($?E#=)6k9F~f{^q$_*z78FF)sq{gq>MGV$Vta?c#K;93S{I zUI5UTM(7g1HB|b`rp;1{8m=(maX0ZH5>T|pyY{VQ3dGZ+m)6Bk>$%_z4j;;am<=@#rX%G3ppBzM({WVR^7R6uU zXMo{U(Q6lx2?_|OoL_Yh){Q7bO|9QpRix!T?6uKB>Y-0?icb^)AWz)^{+nUF(}MSW zlK6RJ6Xxv%Pm3xa@tx0iFE!Y6q_1!pAOK+NsYshoYkhUhXjid$vsZV`B#u(yjf09E zjB?JT9`s?w*Vda|DIBz;HfR6qHv`J|uZ4}3KT(ZkKjC_d+%pz&E6a@Zv}I^XVVv`d z*Awnvzk$K{{Ffh&kRCx0^PjoBR)q-cKesRcPkHcyAnsFrnDRqzA|h8 literal 1333 zcma)+{Xf$Q0LQ;$p=~wfVMWa@T`R6In!7vv;p4fKWSsIvjzn=3->p*A z`xTum$3(2m=v+tTFJU+cRNu8(Oq5}Un0eUY5SO?KE!o$SN6109S)uc>8*gNCp{Ucc z;@D`Ja*ont2vy8AKAODc*7}Bh%2T*Ds?rF;qy}88FB!L+Npdlpk7sQxB->RuEF)81 zP0nri;#cs$LZ%PeRAg}_$H>1$3IuBtRG+ptDdh4OUi#_Js~2-4zBMFxfAa(mj+UBd zSUxy(1)}XP2GUjb1ZSQOaUMnR#_&o36Hq4rfDs^AV1w}gp60E>h6YnTJv}VpKy_^` zZ(@Qrs8Dc4qJ6Nd(f7*B!)`V-I7KlSr2zkIQW7c=ckrMu#37u`#uH1(gK&fwWOIIN zpb~7Sjt&e!r>3Ur(65;&#qjVRG35Xre_`~4{>f`gYVSGuxu=v9Cr*IGnK@x`@p&Cf z#}TPC>#bKji)E5eqth?yyk{{Oh(w&byR5@{VIeIoO<1`%Dm*HxYX#y~QTfZ~DjQ@f zW_5K{a~bdECSYbZHXbm@@D!SAQ+tMnqNjl^Ws_sPNx_k5mzHMh)InofLPFsk{kI6F zIycZG@rWTyF&5J~)voM}uve+oKP=Ymf(i8B!pCZAGBV;bTmW;9`v*Ow_VMlgVJRuw z^1aC>+Tr|}aNmNqHjnrXi_3-e>RBa+%)w|ouH@u^KaJx{pvd4%RdU#6AoqGz_(ZpI z4PF<*GJY2uBfNKy*IEM_tk4Jg`gANTEUZ9ra+nw>*>A@xKK;gwKl0ABvaOT{|2G=c@h{BSRjwFbPY8$vD zmY{Ag8qFizXJ&6OZUHmD=lyCXQ(%UGo3t{Z7pvk+BqSwNOdchAWV_4dAEzoroxyop z-LAx`Rp$z7EO?yl=6F1?cg-|!Z&v@yHhoj0+G-Ok(*jQk*Xxf&q{Xxr8qxZ^voIU} z31P?_60WLY%T3#izy~eDqswu%;X6C1?S2Kj{neUayU(mVBVF4_S3*y8+C^kh3^1LW zJ>dgqI#of&=6AA{%=9uFam0h|??$TZyNCTlLtlAe`y+xjKCwok_k}{5@XsiAN5INz z4ujV9^SFm41NRCN*X&R5eX+|G**dY1V)IbjR#V^qQl{_A2B!UG0;CciD(Ai0M!>+g?^iy-*l6eNAxhdlD=mm#|MaIruydb@tzg3WguBNGgD~c8Lj7*PoDsH;s z3hs9SxqcyV=Q2$I{a6FoS?a%l(|$T{o0j8Ha>FsJKPGpLPp10N0WbcEP)vu`s*Y zbLI#sBn!(hj6xlh<8A!4w1F~e4WU{{}un+75`@lYsv0dl@WF(uCENcL$8sh^^0Z?$h z<@->kUJ#mv2bMx0=^Y^f0(TtiRM0dxocSg+BZOq(R5O037LqVhfOnb;E0-=6ZDZBt z)s~jlUV~t~q?3Hm6uoBqdQI~ZN%&16@O~gs^;Vvt69Ch$=(i-QX6YD7m?*#-^=j?f zisrjV$S_?(76657{h2ckPO`=CixVFlXXKuW0^v8y(D@ zc&_>A7P8rez(WfpI}#w-p(OlMV)!*V!J6SOTCX>w7quesk*+`L5{Tqu&jGpqn%B-po(4g}=;v$sPEx zbiwzWfdVhwxb}JBwHxnH$V9-dmrj%Co+IQ8l%}G3VKjA?JJ2^#87-_#3^yXM4QvD3 zKu8wCa=^*m_oOv>wiGzFCLN{T-2R=1YAGN(*H8B5mO2j&3{6dvwNb;diKMBajt6sS ztV)iWHeL?2AmB)Cw8Cg$4UWWnrz_*R`v{QniZoy8%fNy0J@b1AGB6OA=A%oyG7+#Z zF3mqd$e9?3Y5v0FG@t#7+J#4HK6|2esw@9|Vo%4$vn8zM-zDIwGsBjiI2Kj~N^jIA zEgdUX1$v@aUbnJxxjImK+xmhlQDFsLrJjCEd&2+rAN&ExA8?;1#o7@70000Rw|cw=olo6NwkP;}Mz=sQIs-S@Q$5|pqr7zZ}6vF%7H zK0h7?6P_Rc3q}5)SU4qmTvK|$iXC57bGE9V!Q2%#@QG82?|@@c@PQ-0RCD&KThoG% zoH(fF994IJ6Wj%dO9_4#g8cc-wBS8^eyHaBRQ+O7Ab?kF$XMr)v0)AflL8mX8{X!q zXUx>Jz+**7RFE<=FG#f@Bqg{DZb2Y4mTFZnE8c79n3om7l#a3SaTQ*{m;^0AGRM=n z3Lm?vARr^vBt#`*QZ!@)*fh?Vm{1dyDSa}cMFcP*A*w+#q(dgiv(q85VgLvL(^rJV zsvZV1B0e+U2LuK(L{{b{OI~0o0vI4G^N*%~jfO})zu=jkH-1IJf(JcsT#@h^2M}B+ zeQd5i_lQpX+X&z}PXsNQF-6n~eAXnSEs3!j{EKc($g*DG^RD#;D*aqw~RTH+#Z b?R)SCA{UOLm@|oS00000NkvXXu0mjfFl4g} diff --git a/res/drawable-xhdpi/stat_sys_download_anim3.png b/res/drawable-xhdpi/stat_sys_download_anim3.png deleted file mode 100644 index 6c715f60a05bb8295ae5d4caf2cf29019fc83424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)^=QM58>I5ZM9 zcC6#U92hF1gQkr?`&tlis4`e$Y0nBAD({^x4Cn45K*B5He4#G^`-b<-?;uFPo^oNn zbZJ{60(O-P^G^^`CiawZ{=(xppZto-g-3Bdd7^Tvi{KBjqhrI`6ISx?67bZSTPX+1fpS0@T=9wzW}?8GI6+Yp4aKJ5hDC};C|2qa;0eC4CtmQvvnU`h z_xS9K0-g}ytUC}gCNcC0>4*aIazcWEF(D3X=YWX87ZFJc&A>3EmG{fHi5X^~X;L6< z^vY_vOK1QJ!1fhVFZx(Wi)clC9Z*9LEYD4>B}s^12b%?Y`})OXo3_13S5 z=~6TG)+b_)Z~?^+rH(_HjgKnC??wST924fG!yzhbAmEIcBqtG-tN{;ApVM3n=w%NC roaeq^gr-79Tmn4&oOr1J_8)u#=xEXTwgB%n00000NkvXXu0mjf9Gj_Y diff --git a/res/drawable-xhdpi/stat_sys_download_anim5.png b/res/drawable-xhdpi/stat_sys_download_anim5.png deleted file mode 100644 index 7756933054d982be8e9dbdd0d871227e8a9b97ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 457 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 M9(x4CL6hmf15R|!2><{9 diff --git a/res/drawable/progress_background.xml b/res/drawable/progress_background.xml new file mode 100644 index 0000000000..5f2e8b64c9 --- /dev/null +++ b/res/drawable/progress_background.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/res/drawable/stat_sys_download.xml b/res/drawable/stat_sys_download.xml deleted file mode 100644 index 77ecf8588b..0000000000 --- a/res/drawable/stat_sys_download.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index 99be48928d..2444b62c7c 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -46,8 +46,8 @@ diff --git a/res/layout/media_overview_item.xml b/res/layout/media_overview_item.xml index 8b37e0f125..f02d942140 100644 --- a/res/layout/media_overview_item.xml +++ b/res/layout/media_overview_item.xml @@ -1,8 +1,6 @@ - @@ -10,8 +8,6 @@ android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" - android:scaleType="centerCrop" - android:background="#11ffffff" android:contentDescription="@string/media_preview_activity__image_content_description" /> - + diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index 7884ac040c..75017775b7 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -1,22 +1,25 @@ - + - + - \ No newline at end of file + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 6c139548eb..6d25609aa4 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -100,12 +100,6 @@ - - - - - - diff --git a/src/org/thoughtcrime/securesms/BaseActionBarActivity.java b/src/org/thoughtcrime/securesms/BaseActionBarActivity.java index a3dc4c52e5..565bd46f85 100644 --- a/src/org/thoughtcrime/securesms/BaseActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/BaseActionBarActivity.java @@ -5,11 +5,14 @@ import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.animation.AnimationUtils; import java.lang.reflect.Field; @@ -58,10 +61,8 @@ public abstract class BaseActionBarActivity extends AppCompatActivity { } protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, sharedView, transitionName).toBundle()); - } else { - startActivity(intent); - } + Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName) + .toBundle(); + ActivityCompat.startActivity(this, intent, bundle); } } diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index c2b6cd1e86..300911ce47 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -263,6 +263,7 @@ public class ConversationItem extends LinearLayout { mediaThumbnail.setImageResource(masterSecret, messageRecord.getId(), messageRecord.getDateReceived(), ((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture()); + mediaThumbnail.setShowProgress(!messageRecord.isFailed()); bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } else { mediaThumbnail.setVisibility(View.GONE); @@ -374,7 +375,7 @@ public class ConversationItem extends LinearLayout { contactPhoto.setAvatar(recipient, true); contactPhoto.setVisibility(View.VISIBLE); } - + /// Event handlers private void handleApproveIdentity() { diff --git a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java b/src/org/thoughtcrime/securesms/components/ForegroundImageView.java deleted file mode 100644 index 0139774ff6..0000000000 --- a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.thoughtcrime.securesms.components; - -import android.annotation.TargetApi; -import android.app.ActivityOptions; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build.VERSION_CODES; -import android.support.annotation.NonNull; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; - -import com.makeramen.roundedimageview.RoundedImageView; - -import org.thoughtcrime.securesms.R; - -/** - * https://gist.github.com/chrisbanes/9091754 - */ -public class ForegroundImageView extends RoundedImageView { - - private Drawable mForeground; - - private final Rect mSelfBounds = new Rect(); - private final Rect mOverlayBounds = new Rect(); - - private int mForegroundGravity = Gravity.FILL; - - private boolean mForegroundInPadding = true; - - private boolean mForegroundBoundsChanged = false; - - public ForegroundImageView(Context context) { - super(context); - } - - public ForegroundImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ForegroundImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView, - defStyle, 0); - - mForegroundGravity = a.getInt( - R.styleable.ForegroundImageView_android_foregroundGravity, mForegroundGravity); - - final Drawable d = a.getDrawable(R.styleable.ForegroundImageView_android_foreground); - if (d != null) { - setForeground(d); - } - - mForegroundInPadding = a.getBoolean( - R.styleable.ForegroundImageView_android_foregroundInsidePadding, true); - - a.recycle(); - } - - /** - * Describes how the foreground is positioned. - * - * @return foreground gravity. - * - * @see #setForegroundGravity(int) - */ - public int getForegroundGravity() { - return mForegroundGravity; - } - - /** - * Describes how the foreground is positioned. Defaults to START and TOP. - * - * @param foregroundGravity See {@link android.view.Gravity} - * - * @see #getForegroundGravity() - */ - public void setForegroundGravity(int foregroundGravity) { - if (mForegroundGravity != foregroundGravity) { - if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.START; - } - - if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.TOP; - } - - mForegroundGravity = foregroundGravity; - - - if (mForegroundGravity == Gravity.FILL && mForeground != null) { - Rect padding = new Rect(); - mForeground.getPadding(padding); - } - - requestLayout(); - } - } - - @TargetApi(VERSION_CODES.JELLY_BEAN) - public ActivityOptions getThumbnailTransition() { - return ActivityOptions.makeScaleUpAnimation(this, 0, 0, getWidth(), getHeight()); - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (who == mForeground); - } - - @Override - @TargetApi(VERSION_CODES.HONEYCOMB) - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (mForeground != null) mForeground.jumpToCurrentState(); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mForeground != null && mForeground.isStateful()) { - mForeground.setState(getDrawableState()); - } - } - - /** - * Supply a Drawable that is to be rendered on top of all of the child - * views in the frame layout. Any padding in the Drawable will be taken - * into account by ensuring that the children are inset to be placed - * inside of the padding area. - * - * @param drawable The Drawable to be drawn on top of the children. - */ - public void setForeground(Drawable drawable) { - if (mForeground != drawable) { - if (mForeground != null) { - mForeground.setCallback(null); - unscheduleDrawable(mForeground); - } - - mForeground = drawable; - - if (drawable != null) { - setWillNotDraw(false); - drawable.setCallback(this); - if (drawable.isStateful()) { - drawable.setState(getDrawableState()); - } - if (mForegroundGravity == Gravity.FILL) { - Rect padding = new Rect(); - drawable.getPadding(padding); - } - } else { - setWillNotDraw(true); - } - requestLayout(); - invalidate(); - } - } - - /** - * Returns the drawable used as the foreground of this FrameLayout. The - * foreground drawable, if non-null, is always drawn on top of the children. - * - * @return A Drawable or null if no foreground was set. - */ - public Drawable getForeground() { - return mForeground; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mForegroundBoundsChanged = changed; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mForegroundBoundsChanged = true; - } - - @Override - public void draw(@NonNull Canvas canvas) { - super.draw(canvas); - - if (mForeground != null) { - final Drawable foreground = mForeground; - - if (mForegroundBoundsChanged) { - mForegroundBoundsChanged = false; - final Rect selfBounds = mSelfBounds; - final Rect overlayBounds = mOverlayBounds; - - final int w = getRight() - getLeft(); - final int h = getBottom() - getTop(); - - if (mForegroundInPadding) { - selfBounds.set(0, 0, w, h); - } else { - selfBounds.set(getPaddingLeft(), getPaddingTop(), - w - getPaddingRight(), h - getPaddingBottom()); - } - - Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), - foreground.getIntrinsicHeight(), selfBounds, overlayBounds); - foreground.setBounds(overlayBounds); - } - - foreground.draw(canvas); - } - } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java new file mode 100644 index 0000000000..b4028febf1 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.components; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class SquareFrameLayout extends FrameLayout { + @SuppressWarnings("unused") + public SquareFrameLayout(Context context) { + super(context); + } + + @SuppressWarnings("unused") + public SquareFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused") + public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("unused") + public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //noinspection SuspiciousNameCombination + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } +} diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 3f58cf7ac4..4d580995bd 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -4,56 +4,87 @@ import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.widget.FrameLayout; import com.bumptech.glide.GenericRequestBuilder; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.makeramen.roundedimageview.RoundedImageView; +import com.pnikosis.materialishprogress.ProgressWheel; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.jobs.PartProgressEvent; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; -import org.thoughtcrime.securesms.mms.ThumbnailTransform; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.Util; +import de.greenrobot.event.EventBus; import ws.com.google.android.mms.pdu.PduPart; -public class ThumbnailView extends RoundedImageView { +public class ThumbnailView extends FrameLayout { + private static final String TAG = ThumbnailView.class.getSimpleName(); + + private boolean showProgress = true; + private RoundedImageView image; + private ProgressWheel progress; private ListenableFutureTask slideDeckFuture = null; private SlideDeckListener slideDeckListener = null; private ThumbnailClickListener thumbnailClickListener = null; private String slideId = null; private Slide slide = null; - private Handler handler = new Handler(); public ThumbnailView(Context context) { - super(context); + this(context, null); } public ThumbnailView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } public ThumbnailView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + inflate(context, R.layout.thumbnail_view, this); + image = (RoundedImageView) findViewById(R.id.thumbnail_image); + progress = (ProgressWheel) findViewById(R.id.progress_wheel); + } + + @Override protected void onAttachedToWindow() { + super.onAttachedToWindow(); + EventBus.getDefault().registerSticky(this); + } + + @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + EventBus.getDefault().unregister(this); + } + + @SuppressWarnings("unused") + public void onEventAsync(final PartProgressEvent event) { + if (this.slide != null && event.partId.equals(this.slide.getPart().getPartId())) { + Util.runOnMain(new Runnable() { + @Override public void run() { + progress.setInstantProgress(((float) event.progress) / event.total); + if (event.progress >= event.total) animateOutProgress(); + } + }); + } } public void setImageResource(@Nullable MasterSecret masterSecret, @@ -67,7 +98,7 @@ public class ThumbnailView extends RoundedImageView { String slideId = id + "::" + timestamp; if (!slideId.equals(this.slideId)) { - setImageDrawable(null); + image.setImageDrawable(null); this.slide = null; this.slideId = slideId; } @@ -82,13 +113,24 @@ public class ThumbnailView extends RoundedImageView { } public void setImageResource(@NonNull Slide slide, @Nullable MasterSecret masterSecret) { - if (isContextValid()) { - if (!Util.equals(slide, this.slide)) buildGlideRequest(slide, masterSecret).into(this); - this.slide = slide; - setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide)); - } else { - Log.w(TAG, "Not going to load resource, context is invalid"); + if (Util.equals(slide, this.slide)) { + Log.w(TAG, "Not loading resource, slide was identical"); + return; } + if (!isContextValid()) { + Log.w(TAG, "Not loading resource, context is invalid"); + return; + } + + this.slide = slide; + if (slide.isInProgress() && showProgress) { + progress.spin(); + progress.setVisibility(VISIBLE); + } else { + progress.setVisibility(GONE); + } + buildGlideRequest(slide, masterSecret).into(image); + setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide)); } public void setThumbnailClickListener(ThumbnailClickListener listener) { @@ -99,6 +141,13 @@ public class ThumbnailView extends RoundedImageView { if (isContextValid()) Glide.clear(this); } + public void setShowProgress(boolean showProgress) { + this.showProgress = showProgress; + if (progress.getVisibility() == View.VISIBLE && !showProgress) { + animateOutProgress(); + } + } + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) private boolean isContextValid() { return !(getContext() instanceof Activity) || @@ -110,22 +159,17 @@ public class ThumbnailView extends RoundedImageView { @Nullable MasterSecret masterSecret) { final GenericRequestBuilder builder; - if (slide.getPart().isPendingPush()) { - builder = buildPendingGlideRequest(slide); - } else if (slide.getThumbnailUri() != null) { + if (slide.getThumbnailUri() != null) { builder = buildThumbnailGlideRequest(slide, masterSecret); } else { builder = buildPlaceholderGlideRequest(slide); } - return builder.error(R.drawable.ic_missing_thumbnail_picture); - } - - private GenericRequestBuilder buildPendingGlideRequest(Slide slide) { - return Glide.with(getContext()).load(R.drawable.stat_sys_download_anim0) - .dontTransform() - .skipMemoryCache(true) - .crossFade(); + if (slide.isInProgress() && showProgress) { + return builder; + } else { + return builder.error(R.drawable.ic_missing_thumbnail_picture); + } } private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) { @@ -148,7 +192,7 @@ public class ThumbnailView extends RoundedImageView { } return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri())) - .transform(new ThumbnailTransform(getContext())); + .centerCrop(); } private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) { @@ -157,6 +201,19 @@ public class ThumbnailView extends RoundedImageView { .crossFade(); } + private void animateOutProgress() { + AlphaAnimation animation = new AlphaAnimation(1f, 0f); + animation.setDuration(200); + animation.setAnimationListener(new AnimationListener() { + @Override public void onAnimationStart(Animation animation) { } + @Override public void onAnimationRepeat(Animation animation) { } + @Override public void onAnimationEnd(Animation animation) { + progress.setVisibility(View.GONE); + } + }); + progress.startAnimation(animation); + } + private class SlideDeckListener implements FutureTaskListener { private final MasterSecret masterSecret; @@ -170,14 +227,14 @@ public class ThumbnailView extends RoundedImageView { final Slide slide = slideDeck.getThumbnailSlide(getContext()); if (slide != null) { - handler.post(new Runnable() { + Util.runOnMain(new Runnable() { @Override public void run() { setImageResource(slide, masterSecret); } }); } else { - handler.post(new Runnable() { + Util.runOnMain(new Runnable() { @Override public void run() { Log.w(TAG, "Resolved slide was null!"); @@ -190,7 +247,7 @@ public class ThumbnailView extends RoundedImageView { @Override public void onFailure(Throwable error) { Log.w(TAG, error); - handler.post(new Runnable() { + Util.runOnMain(new Runnable() { @Override public void run() { Log.w(TAG, "onFailure!"); diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 7b8a091003..8ec2419a63 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -721,6 +721,12 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT)); contentValues.remove(ADDRESS); + if (sendRequest.getBody() != null) { + for (int i = 0; i < sendRequest.getBody().getPartsNum(); i++) { + sendRequest.getBody().getPart(i).setInProgress(true); + } + } + long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(), sendRequest.getBody(), contentValues); jobManager.add(new TrimThreadJob(context, threadId)); diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index 938f2036e4..5207ed8eb2 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -57,26 +57,26 @@ import ws.com.google.android.mms.pdu.PduPart; public class PartDatabase extends Database { private static final String TAG = PartDatabase.class.getSimpleName(); - private static final String TABLE_NAME = "part"; - private static final String ROW_ID = "_id"; - private static final String MMS_ID = "mid"; - private static final String SEQUENCE = "seq"; - private static final String CONTENT_TYPE = "ct"; - private static final String NAME = "name"; - private static final String CHARSET = "chset"; - private static final String CONTENT_DISPOSITION = "cd"; - private static final String FILENAME = "fn"; - private static final String CONTENT_ID = "cid"; - private static final String CONTENT_LOCATION = "cl"; - private static final String CONTENT_TYPE_START = "ctt_s"; - private static final String CONTENT_TYPE_TYPE = "ctt_t"; - private static final String ENCRYPTED = "encrypted"; - private static final String DATA = "_data"; - private static final String PENDING_PUSH_ATTACHMENT = "pending_push"; - private static final String SIZE = "data_size"; - private static final String THUMBNAIL = "thumbnail"; - private static final String ASPECT_RATIO = "aspect_ratio"; - private static final String UNIQUE_ID = "unique_id"; + private static final String TABLE_NAME = "part"; + private static final String ROW_ID = "_id"; + private static final String MMS_ID = "mid"; + private static final String SEQUENCE = "seq"; + private static final String CONTENT_TYPE = "ct"; + private static final String NAME = "name"; + private static final String CHARSET = "chset"; + private static final String CONTENT_DISPOSITION = "cd"; + private static final String FILENAME = "fn"; + private static final String CONTENT_ID = "cid"; + private static final String CONTENT_LOCATION = "cl"; + private static final String CONTENT_TYPE_START = "ctt_s"; + private static final String CONTENT_TYPE_TYPE = "ctt_t"; + private static final String ENCRYPTED = "encrypted"; + private static final String DATA = "_data"; + private static final String IN_PROGRESS = "pending_push"; + private static final String SIZE = "data_size"; + private static final String THUMBNAIL = "thumbnail"; + private static final String ASPECT_RATIO = "aspect_ratio"; + private static final String UNIQUE_ID = "unique_id"; private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?"; @@ -86,12 +86,12 @@ public class PartDatabase extends Database { CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " + CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " + CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + - PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + + IN_PROGRESS + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + THUMBNAIL + " TEXT, " + ASPECT_RATIO + " REAL, " + UNIQUE_ID + " INTEGER NOT NULL);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", - "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");", + "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + IN_PROGRESS + ");", }; private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", " @@ -127,7 +127,7 @@ public class PartDatabase extends Database { SQLiteDatabase database = databaseHelper.getWritableDatabase(); part.setContentDisposition(new byte[0]); - part.setPendingPush(false); + part.setInProgress(false); ContentValues values = getContentValuesForPart(part); @@ -275,10 +275,10 @@ public class PartDatabase extends Database { if (!cursor.isNull(encryptedColumn)) part.setEncrypted(cursor.getInt(encryptedColumn) == 1); - int pendingPushColumn = cursor.getColumnIndexOrThrow(PENDING_PUSH_ATTACHMENT); + int inProgressColumn = cursor.getColumnIndexOrThrow(IN_PROGRESS); - if (!cursor.isNull(pendingPushColumn)) - part.setPendingPush(cursor.getInt(pendingPushColumn) == 1); + if (!cursor.isNull(inProgressColumn)) + part.setInProgress(cursor.getInt(inProgressColumn) == 1); int sizeColumn = cursor.getColumnIndexOrThrow(SIZE); @@ -325,7 +325,7 @@ public class PartDatabase extends Database { } contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0); - contentValues.put(PENDING_PUSH_ATTACHMENT, part.isPendingPush() ? 1 : 0); + contentValues.put(IN_PROGRESS, part.isInProgress() ? 1 : 0); contentValues.put(UNIQUE_ID, part.getUniqueId()); return contentValues; @@ -437,7 +437,7 @@ public class PartDatabase extends Database { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Pair partData = null; - if (!part.isPendingPush()) { + if (part.getData() != null || part.getDataUri() != null) { partData = writePartData(masterSecret, part); Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath()); } @@ -457,7 +457,7 @@ public class PartDatabase extends Database { Log.w(TAG, "inserting pre-generated thumbnail"); ThumbnailData data = new ThumbnailData(thumbnail); updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio()); - } else if (!part.isPendingPush()) { + } else if (!part.isInProgress()) { thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId)); } @@ -472,7 +472,7 @@ public class PartDatabase extends Database { Pair partData = writePartData(masterSecret, part, data); part.setContentDisposition(new byte[0]); - part.setPendingPush(false); + part.setInProgress(false); ContentValues values = getContentValuesForPart(part); @@ -488,6 +488,17 @@ public class PartDatabase extends Database { notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); } + public void markPartUploaded(long messageId, PduPart part) { + ContentValues values = new ContentValues(1); + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + + part.setInProgress(false); + values.put(IN_PROGRESS, false); + database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings()); + + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + } + public void updatePartData(MasterSecret masterSecret, PduPart part, InputStream data) throws MmsException { @@ -640,5 +651,20 @@ public class PartDatabase extends Database { public boolean isValid() { return rowId >= 0 && uniqueId >= 0; } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PartId partId = (PartId)o; + + if (rowId != partId.rowId) return false; + return uniqueId == partId.uniqueId; + + } + + @Override public int hashCode() { + return Util.hashCode(rowId, uniqueId); + } } } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 4402aa4a17..85bce1f142 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -3,10 +3,12 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.util.Log; + import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.database.PartDatabase.PartId; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.util.Base64; @@ -15,6 +17,7 @@ import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.textsecure.api.TextSecureMessageReceiver; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener; import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException; @@ -26,6 +29,7 @@ import java.util.List; import javax.inject.Inject; +import de.greenrobot.event.EventBus; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.PduPart; @@ -82,15 +86,19 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId) throws IOException { - PartDatabase database = DatabaseFactory.getPartDatabase(context); - File attachmentFile = null; - PartDatabase.PartId partId = part.getPartId(); + PartDatabase database = DatabaseFactory.getPartDatabase(context); + File attachmentFile = null; + final PartId partId = part.getPartId(); try { attachmentFile = createTempFile(); TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part); - InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile); + InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile, new ProgressListener() { + @Override public void onAttachmentProgress(long total, long progress) { + EventBus.getDefault().postSticky(new PartProgressEvent(partId, total, progress)); + } + }); database.updateDownloadedPart(masterSecret, messageId, partId, part, attachment); } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) { @@ -145,4 +153,5 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable private class InvalidPartException extends Exception { public InvalidPartException(Exception e) {super(e);} } + } diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index c85d1fe1bd..f3a5b14670 100644 --- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -96,7 +96,7 @@ public class AvatarDownloadJob extends MasterSecretJob { destination.deleteOnExit(); - socket.retrieveAttachment(relay, contentLocation, destination); + socket.retrieveAttachment(relay, contentLocation, destination, null); return destination; } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 3b36e6d48d..90b78b906d 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -103,7 +103,8 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje FileInputStream contactsFileStream = new FileInputStream(contactsFile); TextSecureAttachmentStream attachmentStream = new TextSecureAttachmentStream(contactsFileStream, "application/octet-stream", - contactsFile.length()); + contactsFile.length(), + null); try { messageSender.sendMessage(TextSecureSyncMessage.forContacts(attachmentStream)); @@ -117,7 +118,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje try { Uri displayPhotoUri = Uri.withAppendedPath(uri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO); AssetFileDescriptor fd = context.getContentResolver().openAssetFileDescriptor(displayPhotoUri, "r"); - return Optional.of(new TextSecureAttachmentStream(fd.createInputStream(), "image/*", fd.getLength())); + return Optional.of(new TextSecureAttachmentStream(fd.createInputStream(), "image/*", fd.getLength(), null)); } catch (IOException e) { Log.w(TAG, e); } @@ -140,7 +141,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje byte[] data = cursor.getBlob(0); if (data != null) { - return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(data), "image/*", data.length)); + return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(data), "image/*", data.length, null)); } } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 7f8b1e9cd5..5fdaf13f92 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -95,7 +95,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject FileInputStream contactsFileStream = new FileInputStream(contactsFile); TextSecureAttachmentStream attachmentStream = new TextSecureAttachmentStream(contactsFileStream, "application/octet-stream", - contactsFile.length()); + contactsFile.length(), + null); messageSender.sendMessage(TextSecureSyncMessage.forGroups(attachmentStream)); } @@ -105,7 +106,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject if (avatar == null) return Optional.absent(); return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(avatar), - "image/*", avatar.length)); + "image/*", avatar.length, null)); } private File createTempFile(String prefix) throws IOException { diff --git a/src/org/thoughtcrime/securesms/jobs/PartProgressEvent.java b/src/org/thoughtcrime/securesms/jobs/PartProgressEvent.java new file mode 100644 index 0000000000..7a22aaef6b --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/PartProgressEvent.java @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms.jobs; + +import org.thoughtcrime.securesms.database.PartDatabase.PartId; + +public class PartProgressEvent { + public PartId partId; + public long total; + public long progress; + + public PartProgressEvent(PartId partId, long total, long progress) { + this.partId = partId; + this.total = total; + this.progress = progress; + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index ead5f892c2..1fbe8d51a1 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; +import org.thoughtcrime.securesms.database.PartDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.PartParser; @@ -30,6 +31,7 @@ import java.util.List; import javax.inject.Inject; import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.SendReq; import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory; @@ -69,6 +71,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { database.markAsPush(messageId); database.markAsSecure(messageId); database.markAsSent(messageId, "push".getBytes(), 0); + updatePartsStatus(message.getBody()); } catch (InsecureFallbackApprovalException ifae) { Log.w(TAG, ifae); database.markAsPendingInsecureSmsFallback(messageId); @@ -97,6 +100,13 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { notifyMediaMessageDeliveryFailed(context, messageId); } + private void updatePartsStatus(PduBody body) { + if (body == null) return; + PartDatabase database = DatabaseFactory.getPartDatabase(context); + for (int i = 0; i < body.getPartsNum(); i++) { + database.markPartUploaded(messageId, body.getPart(i)); + } + } private void deliver(MasterSecret masterSecret, SendReq message) throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index 027148092f..bddae1c39d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -10,13 +10,12 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.messages.TextSecureAttachment; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener; import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream; import org.whispersystems.textsecure.api.push.TextSecureAddress; import org.whispersystems.textsecure.api.util.InvalidNumberException; @@ -26,6 +25,7 @@ import java.io.InputStream; import java.util.LinkedList; import java.util.List; +import de.greenrobot.event.EventBus; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.SendReq; @@ -59,16 +59,19 @@ public abstract class PushSendJob extends SendJob { List attachments = new LinkedList<>(); for (int i=0;i