From 80ce83ef9dcbcf5c846bc2b06fb8d43a0e2813aa Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Mon, 24 Aug 2015 15:24:31 -0700 Subject: [PATCH] Media download controls Closes #3991 // FREEBIE --- build.gradle | 1 + .../ic_file_download_white_36dp.png | Bin 0 -> 656 bytes res/drawable-hdpi/ic_forum_black_32dp.png | Bin 0 -> 495 bytes res/drawable-hdpi/ic_forum_grey_32dp.png | Bin 0 -> 512 bytes res/drawable-hdpi/ic_textsms_black_32dp.png | Bin 0 -> 583 bytes res/drawable-hdpi/ic_textsms_grey_32dp.png | Bin 0 -> 687 bytes .../ic_file_download_white_36dp.png | Bin 0 -> 479 bytes res/drawable-mdpi/ic_forum_black_32dp.png | Bin 0 -> 422 bytes res/drawable-mdpi/ic_forum_grey_32dp.png | Bin 0 -> 437 bytes res/drawable-mdpi/ic_textsms_black_32dp.png | Bin 0 -> 435 bytes res/drawable-mdpi/ic_textsms_grey_32dp.png | Bin 0 -> 517 bytes .../ic_file_download_white_36dp.png | Bin 0 -> 653 bytes res/drawable-xhdpi/ic_forum_black_32dp.png | Bin 0 -> 620 bytes res/drawable-xhdpi/ic_forum_grey_32dp.png | Bin 0 -> 640 bytes res/drawable-xhdpi/ic_textsms_black_32dp.png | Bin 0 -> 636 bytes res/drawable-xhdpi/ic_textsms_grey_32dp.png | Bin 0 -> 751 bytes .../ic_file_download_white_36dp.png | Bin 0 -> 1059 bytes res/drawable-xxhdpi/ic_forum_black_32dp.png | Bin 0 -> 759 bytes res/drawable-xxhdpi/ic_forum_grey_32dp.png | Bin 0 -> 782 bytes res/drawable-xxhdpi/ic_textsms_black_32dp.png | Bin 0 -> 805 bytes res/drawable-xxhdpi/ic_textsms_grey_32dp.png | Bin 0 -> 958 bytes .../ic_file_download_white_36dp.png | Bin 0 -> 615 bytes res/drawable-xxxhdpi/ic_forum_black_32dp.png | Bin 0 -> 551 bytes res/drawable-xxxhdpi/ic_forum_grey_32dp.png | Bin 0 -> 579 bytes .../ic_textsms_black_32dp.png | Bin 0 -> 431 bytes res/drawable-xxxhdpi/ic_textsms_grey_32dp.png | Bin 0 -> 461 bytes res/layout/thumbnail_view.xml | 8 + res/values/arrays.xml | 25 +++ res/values/attrs.xml | 1 + res/values/strings.xml | 5 + res/values/themes.xml | 8 +- res/xml/preferences.xml | 6 +- res/xml/preferences_chats.xml | 48 ++++++ res/xml/preferences_storage.xml | 21 --- .../securesms/ApplicationContext.java | 12 +- .../ApplicationPreferencesActivity.java | 16 +- .../securesms/ConversationItem.java | 9 +- .../securesms/DatabaseUpgradeActivity.java | 33 ++++ .../securesms/components/ThumbnailView.java | 41 +++-- .../securesms/database/MmsDatabase.java | 2 +- .../securesms/database/PartDatabase.java | 65 ++++++-- .../securesms/jobs/AttachmentDownloadJob.java | 41 +++-- .../securesms/jobs/PushDecryptJob.java | 21 ++- .../requirements/MediaNetworkRequirement.java | 99 ++++++++++++ .../MediaNetworkRequirementProvider.java | 18 +++ .../securesms/mms/IncomingMediaMessage.java | 8 +- src/org/thoughtcrime/securesms/mms/Slide.java | 8 +- .../preferences/ChatsPreferenceFragment.java | 143 ++++++++++++++++++ .../StoragePreferenceFragment.java | 96 ------------ .../securesms/util/MediaUtil.java | 7 + .../securesms/util/ServiceUtil.java | 5 + .../securesms/util/TextSecurePreferences.java | 35 +++++ src/org/thoughtcrime/securesms/util/Util.java | 3 +- .../com/google/android/mms/pdu/PduPart.java | 23 ++- 54 files changed, 605 insertions(+), 203 deletions(-) create mode 100644 res/drawable-hdpi/ic_file_download_white_36dp.png create mode 100644 res/drawable-hdpi/ic_forum_black_32dp.png create mode 100644 res/drawable-hdpi/ic_forum_grey_32dp.png create mode 100644 res/drawable-hdpi/ic_textsms_black_32dp.png create mode 100644 res/drawable-hdpi/ic_textsms_grey_32dp.png create mode 100644 res/drawable-mdpi/ic_file_download_white_36dp.png create mode 100644 res/drawable-mdpi/ic_forum_black_32dp.png create mode 100644 res/drawable-mdpi/ic_forum_grey_32dp.png create mode 100644 res/drawable-mdpi/ic_textsms_black_32dp.png create mode 100644 res/drawable-mdpi/ic_textsms_grey_32dp.png create mode 100644 res/drawable-xhdpi/ic_file_download_white_36dp.png create mode 100644 res/drawable-xhdpi/ic_forum_black_32dp.png create mode 100644 res/drawable-xhdpi/ic_forum_grey_32dp.png create mode 100644 res/drawable-xhdpi/ic_textsms_black_32dp.png create mode 100644 res/drawable-xhdpi/ic_textsms_grey_32dp.png create mode 100644 res/drawable-xxhdpi/ic_file_download_white_36dp.png create mode 100644 res/drawable-xxhdpi/ic_forum_black_32dp.png create mode 100644 res/drawable-xxhdpi/ic_forum_grey_32dp.png create mode 100644 res/drawable-xxhdpi/ic_textsms_black_32dp.png create mode 100644 res/drawable-xxhdpi/ic_textsms_grey_32dp.png create mode 100644 res/drawable-xxxhdpi/ic_file_download_white_36dp.png create mode 100644 res/drawable-xxxhdpi/ic_forum_black_32dp.png create mode 100644 res/drawable-xxxhdpi/ic_forum_grey_32dp.png create mode 100644 res/drawable-xxxhdpi/ic_textsms_black_32dp.png create mode 100644 res/drawable-xxxhdpi/ic_textsms_grey_32dp.png create mode 100644 res/xml/preferences_chats.xml delete mode 100644 res/xml/preferences_storage.xml create mode 100644 src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java create mode 100644 src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java create mode 100644 src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java delete mode 100644 src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java diff --git a/build.gradle b/build.gradle index 516f4d3753..422641c57d 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,7 @@ dependencies { compile 'org.whispersystems:libpastelog:1.0.6' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'org.whispersystems:textsecure-android:1.6.2' + compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' diff --git a/res/drawable-hdpi/ic_file_download_white_36dp.png b/res/drawable-hdpi/ic_file_download_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9f23ce8eeca7c8eef1b76a8b9b40a1992d37a515 GIT binary patch literal 656 zcmV;B0&o3^P)qJU~QEvBj9LkX$2}J0VdIwqU|RMHl!`Z+fN!S zrY%+4PXjEZE!Emj3yjm2s_mx<*4LKSDz=|CSXW!BwqFV`KjDoBr`mq0z?v$jUMaHV zv->vDpBcaK|KE+^6W`XL3BVrXm}d}mC?}0$q$c@CN=a5S6wDnU^qqNW$t_699UKbA zp7`A2g3}kskHa5**W77;aHa!FY0RR7GfgVIOVAkgV0000P1CPt~U5%GpdlZbbm&NOWruI)d|kHF4(+cS-X1 zpi=hA^3z{l+3U&NF6=TR3p=;hb_!NVaj`^inV0O4X0jA#dhwa*#YrvaAgK;^fr=xb z83{O)ic`|qB`O}xjE@2-C#X3CC@l~oU`>ez!Xd{T$qAcKLU6*0j!Ifq_6k()T5!e( z6ct77vP6SZDgsU%zKr0E8pVr4?V_Z>#})lGg8zbw(^7(fyNw{A1pKJ*ULSG7DJRLCvIQ`{fybddCIsBO05MauRmYRk(ndWn%A_|gh zax(fyddL%$o`8xQCotslRT2Nmca9kp@w26rLT(5p8bpOC-p(n57%-1SCs9zQ#j}gI llw%@rc~3X_qfz0n;S+&a{=u7t6n6js002ovPDHLkV1kXR+av%0 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_forum_grey_32dp.png b/res/drawable-hdpi/ic_forum_grey_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..37c69a18a5444b5d353c8e1467d411197b8b8869 GIT binary patch literal 512 zcmV+b0{{JqP)6oy|$u*`6S-e>0u?J-K?_HZlfD}}V8atBqhHg??6tLkEuc(fLb`v1e=@BJ?h z%I3&t>HnFw`kSja3|*QKba@ksu!>NI=Fhr<`zRwv)7oGinx0n43Dk z(zWZ30T(RMPmRy36ba6m=)|?gr2$+p)laPPd6^R6v_%^Q_z#$Ho(j-n*8#M8AG*VF zy2FV8=vMKQH8g?K;h#u^wECZ;V#P5NJhnSQ%PpQqB8}T&eW~$S*jdN!Lzmq@s_0WS;4eVkE?@sWC*x_AlV*;Y1MWOod zNf=Y>qMI@A^=##U2!KAZ&Dr2dddyQijUwzIK>VtPBEp({*J2b9;jkqY;?}g9ARWTE zybwu;cx-$cfXb{8*Uw*nl4Q~~>gK-R-jE9u8=!BtV65$1{aPmg0000T1d$3@20RT?EK+fW$o)WY%?OET*aLA2POz0UL{Q&Ja#Gj{PGA8l1*x$B9Uic)!2q>@U3Q2( zV0$|Ozb`@kfMj3yu%1!Je?x+tp1JQk99Vnc1buquygWgHa-WWQ5k0H`8?C(&Ytds7 z9rJx#QR*tnQSN|g4qdDO%K%u0wdgVjOm}QWX@a|8>NvqF09H9c2TXS-CfG3!!8|8e z0l*3;m{*-3p?u}8`M#gTN7jDi7r}k*n!lo?5(L#;E+E;rz>Kv}Z;w9SHo@N>u)TGH zwqKb$Ew?FxNV0%d2+|P2?sf z(E_|Yy$bHq7Jom6X>E7 z0WMD9(5>knW-OF6eQ^$-A$Z_N#WS44=}QHL!r0>aq1(>__p6YALJEW?AdqLH`}J8c z^k8uV<~0OVv*lA`!4u!*0T2N+TMGHapTLzfAv2kzUD~oycwr6@H3TuaGHVet7m0VN z^Rp8C==2O?vSsExF>3|60NAPkf|wl46e>ttbGZNrtpNonDp98O0sa65(E>gY z+F*lCkWd7B+25uOM|j1`=BPEIU zucU75`8*jr$<$h_YEtvp094b$ep;m`(}J_puWP$yxfybqaWl9C&($#tG*iCq8T=CT z;mKtv!xKENZCY#6hvnQ1`I+v(UuZgpZ`+YRES20crzYszH^x+Tw^HW>sBYPxfKuYr z(4^;_Qx_VqpOAJV1u{M7Ln|m;i6#Y*3v>a#2EVIr#%|-AOdZR@AgKHO z1b&T19N47XW91)s!`KA5+TfTv7MHt&E^xp-_!DfvEt<-=8@zUp?F&vU4ori`6%Kp| zKw?1tc);@&8A}9|10{mV0pdW3VRC^|P-2)8pdlzROes(dN(>_b3Ii@}r=VKKfl^^2){HP+e z77OBEz-HicAqdrs#9#sW!rFr0)VLYik{CafD(YXaq3TvybzdDPpp>cu`VIg9|Nja4 V>Maba)7bz3002ovPDHLkV1mFp+{FL@ literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_forum_black_32dp.png b/res/drawable-mdpi/ic_forum_black_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..7840302c1c253163a75efb9dc3321e0ec42e1983 GIT binary patch literal 422 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_forum_grey_32dp.png b/res/drawable-xxhdpi/ic_forum_grey_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..07031d1062f617f7955106be7073c5b05a7af752 GIT binary patch literal 782 zcmV+p1M&QcP)@a6HB=*i!r`4luhs;xxX{r8K z3A|Kqe!B*mnkSV?rQ*SQO}|O?XgE8WUCples>jE(F`~>=*8bgB*46Ac)+5-(Bk%xG zcE&pNkk{xbI9$XsQ~)sO0d87G4-8kEb1){bl?bMa1TI^L9IiGq3OKyLE~07@xKcBK zLmmKj!~_-xaJ6=T!q-deQ~(7~00mHhnXLfUBm)40b!el7c5n;6$SX6c0Bpwte8nep zgFC+701BW03ZMWApa2S>016N{IFbb5GJ&|oBo83C$tMfIp^A!K zZnIv9+pJOmT$YGmVU$P~Ah!P$lfJ6(Wbg#eVI;OM1Hirtu=jp!t=O9=N(3Ob@34;q zBn`l!yvz1ssi{}6lOsxPf77y^p;z=!mIC=I_?ZVcOK)!GQj+5v2 z8IARE?@GuUuo@$HwJ3n;77x~A5?|0kSJWMRDSKEjz;u%bZG4BT&kF&#VjX(ZO&+w+ z0iyuRdoc+V)BWqBjV>Ix$q8Jxj3Tz57QjHcgs_JQW*UG_(-W}vFik78nhjt)z_?vG z13(*J0suDvU?IXfyhjlyL<0dbGb4b(ML0}>1uc<46o&BzPShMq2*3-YIE#1aq9bq# z(TxZ~0OfcUx}TjQ%K!iX M07*qoM6N<$f-nU~t^fc4 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_textsms_black_32dp.png b/res/drawable-xxhdpi/ic_textsms_black_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8b2b0f79b3dde46649148022d892578a12d417 GIT binary patch literal 805 zcmV+=1KRwFP)j3KH#@%b-&C@)vi`TSg)OZ_;L;ZCNts*COXid@W?<%`@Sjhcm(6VeX2UrY zK%a`|`4At0KjsB0`oKmJwCV2S@VO=fAh-upz6~a_36>G~}&r?$4ak%&pq~3f(^q6&(Of@VrCC8Js60vR9!WPl8i0S0LUxPJip zsh-H10Fd+u9a}qrLF5U1YF+_brkH@k%Mu68-C&E<5^(=~JF#4AN`RCA9PKj>FW(@L z5`YUo$9O#F!}4pRbbApy1gxHavPKUQ)x8U;?QPj{(WF8}H jj+zvnH`!zjLX}#2v?}{s|hi_31CM;JB5Mk&zpBlWt0eUerY#{+F_E{PpP_)=q0jZf8hEuWaqG zetpKi5e`+A%*aXa^nIg>s;o-naH{0_8}-gBBPUfd2itlap#T2-OuZqR*p5btX1$T0 z9eBX&?!fB5D=%#&6h^3ovQSsVxEiv8JM)KyJSP|K{?M1}LkY zQ3o&pQzn6s0SYDoGzl0017H9QKv-n}{xM(3LI&88D;^j?l3i@DlI%pQXSaolo#6Lf zZ#9qv9P&73@g#D$PREAtM2CDkt)56;d)~gj+N}nj?z?>wdE9%;fh5o&k3x?yh0DGZ zE9C2X_ODxC?bb4~CUBG?9zy_!JO+RX-^*t=Byb}D#sSuzc;kiyvcB61@)%FR02qL1 z17HHvHHxO0pBKf0uuwu*H3ofB`s?G60ST zP%sIE1|U6vl1acMU;qq&0Wbg&|6d7LYXIwbNFb>#z?A&Q0&w(A;0N{FbDAa~B)R$i z7eC$s;KVfp5Yn@YtuN30B?}~t07SR_6``tgA^}Vc;NM7nJ1rDSltR`V+d=iiRPn-c zd`Ili65uheK7)o(wwhF5y*70=Kq+>B1a{`i9KhFp-KiOy7P-SMy>*` zLrqKmz=|Use5|RnriPkT-u|05Q*+sHmDQ!^0($0J~l&wTC{eX85;HTF6NNY=7v3-$7ToF@U|>w%-p%Axg&G gBm^fTBO{~#09I4z;(o@C0000007*qoM6N<$f;LRC;{X5v literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_file_download_white_36dp.png b/res/drawable-xxxhdpi/ic_file_download_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e261f9a870240a3b8e9cf7ad7de7eff6c858ce13 GIT binary patch literal 615 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t)J zFfiWtba4!+xb^nVZLh-)BCHploL(EhuBlZiE5A5TGuwHdv{8(nFK4H1?#@7ohT@r0Bavzm+X|(yM{jzRRz`UNd zZu)@+{^#P>^lQvf&)$5?dCGyw+h?y=_j-6zru>SK>c=PNnqPHXs@TMyX6n*u^Hca} z&7zQbT@$nQ0*(C7`dIaA%w3+9G2c1lKydjgLI03L!If`>y+e)!SL=znha3y8Es$^y zIT3vS-zLA1QxxbU`!f`l?C%$YbYSS(ol;vFIqfT8@w$-!(j m6F5r1u>lWwNCe3$&$l)d7PvYuWD79<89ZJ6T-G@yGywn)eBG%4 literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_forum_black_32dp.png b/res/drawable-xxxhdpi/ic_forum_black_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8063a274fb4b9eae7aa457a89cfe8e4d69b87375 GIT binary patch literal 551 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2{2-FuwD2aSW+oe0$CGpNOM~>%->V z){>JZDH>Y7)7yPucePp5ZuK2{%_9B<*OQn(fB5D9_t(AR@PPPFT^7&h6rZzvZZi8U zro;gQxAMCIq78@lyxR2qPNJ~02*bI_hh!5Xelwn%@MvSS?1Pn74o1jY zkaWYD0n7pn0t^gkh2S$Z`zn_+A$vf11|83ZHvGq-o zUBi!e{f`vabKQJChvDK~`LotRiKiI^?*F~GCrj0#;718V)AQF1Eama+3OjyVF&w;m zosr|+K7j+w%MRr;?#WX>AoIhwVf$N_A9vk8MMx(w9N)s2AUA(2V}dn^U~I2EvU=Vc z4hD%69&XFqu+KZm8~IL6rZzvJ||i2 zcZIie+U~QDqHO>3@kYMWe!cjIg@g09>LWdm80{`i_*f>we__Jcz7@=K!q_cR8JHyw zFt8akF!CfonGRiwJ%66Ft!9vUa@79Yl<1s<2}}nfrK0ZIwOz44pedD6YkiebZZ)$B zx<-&04gdMkj60B7+MxW3;r3QWIipy~2i)1pC5(BS43yLxvezV3_HOI>+0%J~^MTQ1 zrP*2h1rs#|cQJ&|kXp!HF!dymd3~k;klFlEa1+DJpFr*mfrVTX-qmd@T;$OZ_Rg+W z;@}CNvVCB-@{aS}KHoM%*p<%CdfI#ouI@hX5qHJj!KwzNABf+bW?HgapL@c)eL4=i zes5yL z-iFlRiC7g!Z2#OATlT}I(Ax0Cg`7H}*Uzf12`DxI0c U8?}8sFmW(=y85}Sb4q9e06Bf?KmY&$ literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_textsms_black_32dp.png b/res/drawable-xxxhdpi/ic_textsms_black_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c0712d0bc265ee9b2fef2a659eb3494ab5889ca8 GIT binary patch literal 431 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2{2-FqV0`IEGX(zP)fXQ94n?^-`AeF2@8lIKtASKhh>+QrR}3H3q}<2Jifa(39d1g5pg-u?2bt#P9#&R>bn+#d@mdKI;Vst09A{mN&o-= literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_textsms_grey_32dp.png b/res/drawable-xxxhdpi/ic_textsms_grey_32dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f47cc38adfb64832e06c3976aa1e0094db886753 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2{2-Fi!DwaSW+od~?S=O3P8i;o)wT zw6wbmf7L8w{cyEqPI9mB+-VOwI1`y$R&yJeTv)cM_}zp*%yT)MT!7$9+_N3mwqN#` zY;UshR%C91AH$7xk4hJul%Mb5y?s;U)vQH%K5KPfhue5xnX7s4#4c9t=?qL$OTDkG zomhJ6`6tWk3}-f4PTLadHFZ)p!_~a4+L^EWe<@#Qcyq*0CAWX|@u}GiA76XiV&XL^ zEC9L~3hZ>RTrh@lkZ6TV91ILbyMBFne)mxnBa%czEi#Yc29v@y2?mCL|B#e`nF5(l z%n$s1RQJ%dVeXC2>kM1V*1k1uurz6pWA5mv;1O7}hi!$K%N|yau%rL){hJ)K=5G3C z?MI3ZOUi<>mq)zI=l*bsPZsC~CI=py`Z*jzopr0Lv4scK`qY literal 0 HcmV?d00001 diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index ca2d0cf813..80590293de 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -20,4 +20,12 @@ android:layout_height="wrap_content" android:layout_gravity="top|right" android:layout="@layout/thumbnail_view_remove_button" /> + + diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 7015966840..1461817f1d 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -193,4 +193,29 @@ contact none + + + + image + audio + video + + + + @string/arrays__images + @string/arrays__audio + @string/arrays__video + + + + image + + + + image + audio + video + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index b59dd40a11..cbada251db 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -106,6 +106,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index 5df9a2cf19..296ce0b58a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -762,6 +762,10 @@ Name only Neither + Images + Audio + Video + %d hour @@ -847,6 +851,7 @@ Request a delivery report for each SMS message you send Automatically delete older messages once a conversation thread exceeds a specified length Delete old messages + Chats and media Conversation length limit Trim all threads now Scan through all conversation threads and enforce conversation length limits diff --git a/res/values/themes.xml b/res/values/themes.xml index 26b8ed6262..18f90be176 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -169,11 +169,11 @@ #ff1d85d7 - @drawable/ic_message_black + @drawable/ic_textsms_black_32dp @drawable/ic_notifications_black @drawable/ic_app_protection_black @drawable/ic_brightness_6_black - @drawable/ic_delete_black + @drawable/ic_forum_black_32dp @drawable/ic_devices_black_48dp @drawable/ic_advanced_black @@ -281,11 +281,11 @@ @color/textsecure_primary_dark - @drawable/ic_message_gray + @drawable/ic_textsms_grey_32dp @drawable/ic_notifications_gray @drawable/ic_app_protection_gray @drawable/ic_brightness_6_gray - @drawable/ic_delete_gray + @drawable/ic_forum_grey_32dp @drawable/ic_devices_grey600_48dp @drawable/ic_advanced_gray diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 79ba260040..459324a05b 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -17,9 +17,9 @@ android:title="@string/preferences__appearance" android:icon="?pref_ic_appearance"/> - + diff --git a/res/xml/preferences_chats.xml b/res/xml/preferences_chats.xml new file mode 100644 index 0000000000..ec5e4c70b4 --- /dev/null +++ b/res/xml/preferences_chats.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/preferences_storage.xml b/res/xml/preferences_storage.xml deleted file mode 100644 index 83a1d88cc9..0000000000 --- a/res/xml/preferences_storage.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 97565aa64d..2eeb5bd210 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider; +import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider; import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobManager; @@ -49,9 +50,11 @@ import dagger.ObjectGraph; */ public class ApplicationContext extends Application implements DependencyInjector { - private JobManager jobManager; + private JobManager jobManager; private ObjectGraph objectGraph; + private MediaNetworkRequirementProvider mediaNetworkRequirementProvider = new MediaNetworkRequirementProvider(); + public static ApplicationContext getInstance(Context context) { return (ApplicationContext)context.getApplicationContext(); } @@ -103,11 +106,16 @@ public class ApplicationContext extends Application implements DependencyInjecto .withJobSerializer(new EncryptingJobSerializer()) .withRequirementProviders(new MasterSecretRequirementProvider(this), new ServiceRequirementProvider(this), - new NetworkRequirementProvider(this)) + new NetworkRequirementProvider(this), + mediaNetworkRequirementProvider) .withConsumerThreads(5) .build(); } + public void notifyMediaControlEvent() { + mediaNetworkRequirementProvider.notifyMediaControlEvent(); + } + private void initializeDependencyInjection() { this.objectGraph = ObjectGraph.create(new TextSecureCommunicationModule(this), new AxolotlStorageModule(this)); diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 1ec02f8868..d9866ea003 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment; import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment; import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment; -import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment; +import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -54,7 +54,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications"; private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection"; private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance"; - private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage"; + private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats"; private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices"; private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced"; @@ -130,8 +130,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_APP_PROTECTION)); this.findPreference(PREFERENCE_CATEGORY_APPEARANCE) .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_APPEARANCE)); - this.findPreference(PREFERENCE_CATEGORY_STORAGE) - .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_STORAGE)); + this.findPreference(PREFERENCE_CATEGORY_CHATS) + .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_CHATS)); // this.findPreference(PREFERENCE_CATEGORY_DEVICES) // .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_DEVICES)); this.findPreference(PREFERENCE_CATEGORY_ADVANCED) @@ -154,8 +154,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setSummary(AppProtectionPreferenceFragment.getSummary(getActivity())); this.findPreference(PREFERENCE_CATEGORY_APPEARANCE) .setSummary(AppearancePreferenceFragment.getSummary(getActivity())); - this.findPreference(PREFERENCE_CATEGORY_STORAGE) - .setSummary(StoragePreferenceFragment.getSummary(getActivity())); + this.findPreference(PREFERENCE_CATEGORY_CHATS) + .setSummary(ChatsPreferenceFragment.getSummary(getActivity())); } private class CategoryClickListener implements Preference.OnPreferenceClickListener { @@ -184,8 +184,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA case PREFERENCE_CATEGORY_APPEARANCE: fragment = new AppearancePreferenceFragment(); break; - case PREFERENCE_CATEGORY_STORAGE: - fragment = new StoragePreferenceFragment(); + case PREFERENCE_CATEGORY_CHATS: + fragment = new ChatsPreferenceFragment(); break; case PREFERENCE_CATEGORY_DEVICES: Intent intent = new Intent(getActivity(), DeviceListActivity.class); diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index f7cad6b2f8..408fbcd1df 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.database.PartDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; @@ -156,6 +157,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien if (mediaThumbnail != null) { mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener()); mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener()); + mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener()); } } @@ -275,7 +277,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien mediaThumbnail.setImageResource(masterSecret, messageRecord.getId(), messageRecord.getDateReceived(), ((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture()); - mediaThumbnail.setShowProgress(!messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending())); + mediaThumbnail.hideControls(messageRecord.isFailed() || (messageRecord.isOutgoing() && !messageRecord.isPending())); bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } else { mediaThumbnail.setVisibility(View.GONE); @@ -409,6 +411,11 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien } } + private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener { + @Override public void onClick(View v, Slide slide) { + DatabaseFactory.getPartDatabase(context).setTransferState(messageRecord.getId(), slide.getPart().getPartId(), PartDatabase.TRANSFER_PROGRESS_STARTED); + } + } private class ThumbnailClickListener implements ThumbnailView.ThumbnailClickListener { private void fireIntent(Slide slide) { Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType()); diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java index f149d60f7e..cbc09e40d4 100644 --- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java +++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java @@ -32,7 +32,11 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase.Reader; import org.thoughtcrime.securesms.database.PushDatabase; +import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.PushDecryptJob; @@ -41,10 +45,14 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.VersionTracker; import java.io.File; +import java.util.List; import java.util.SortedSet; import java.util.TreeSet; +import ws.com.google.android.mms.pdu.PduPart; + public class DatabaseUpgradeActivity extends BaseActivity { + private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName(); public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46; public static final int MMS_BODY_VERSION = 46; @@ -57,6 +65,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 131; public static final int MIGRATE_SESSION_PLAINTEXT = 136; public static final int CONTACTS_ACCOUNT_VERSION = 136; + public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 146; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); @@ -68,6 +77,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { add(NO_DECRYPT_QUEUE_VERSION); add(PUSH_DECRYPT_SERIAL_ID_VERSION); add(MIGRATE_SESSION_PLAINTEXT); + add(MEDIA_DOWNLOAD_CONTROLS_VERSION); }}; private MasterSecret masterSecret; @@ -205,9 +215,32 @@ public class DatabaseUpgradeActivity extends BaseActivity { .add(new DirectoryRefreshJob(getApplicationContext())); } + if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) { + schedulePendingIncomingParts(context); + } + return null; } + private void schedulePendingIncomingParts(Context context) { + MmsDatabase db = DatabaseFactory.getMmsDatabase(context); + List pendingParts = DatabaseFactory.getPartDatabase(context).getPendingParts(); + + Log.w(TAG, pendingParts.size() + " pending parts."); + for (PduPart part : pendingParts) { + final Reader reader = db.readerFor(masterSecret, db.getMessage(part.getMmsId())); + final MessageRecord record = reader.getNext(); + + if (record != null && !record.isOutgoing() && record.isPush()) { + Log.w(TAG, "queuing new attachment download job for incoming push part."); + ApplicationContext.getInstance(context) + .getJobManager() + .add(new AttachmentDownloadJob(context, part.getMmsId(), part.getPartId())); + } + reader.close(); + } + } + private void scheduleMessagesInPushDatabase(Context context) { PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); Cursor pushReader = null; diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 3931d29c4c..34ebf5e22c 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -31,6 +31,7 @@ import com.pnikosis.materialishprogress.ProgressWheel; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.PartDatabase; import org.thoughtcrime.securesms.jobs.PartProgressEvent; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.RoundedCorners; @@ -47,16 +48,18 @@ import ws.com.google.android.mms.pdu.PduPart; public class ThumbnailView extends FrameLayout { private static final String TAG = ThumbnailView.class.getSimpleName(); - private boolean showProgress = true; + private boolean hideControls; private ImageView image; private ProgressWheel progress; private ImageView removeButton; + private ImageButton downloadButton; private int backgroundColorHint; private int radius; private ListenableFutureTask slideDeckFuture = null; private SlideDeckListener slideDeckListener = null; private ThumbnailClickListener thumbnailClickListener = null; + private ThumbnailClickListener downloadClickListener = null; private String slideId = null; private Slide slide = null; @@ -68,11 +71,13 @@ public class ThumbnailView extends FrameLayout { this(context, attrs, 0); } - public ThumbnailView(Context context, AttributeSet attrs, int defStyle) { + public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); inflate(context, R.layout.thumbnail_view, this); - radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); - image = (ImageView) findViewById(R.id.thumbnail_image); + radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); + image = (ImageView) findViewById(R.id.thumbnail_image); + progress = (ProgressWheel) findViewById(R.id.progress_wheel); + downloadButton = (ImageButton) findViewById(R.id.download_button); if (attrs != null) { TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); @@ -162,15 +167,24 @@ public class ThumbnailView extends FrameLayout { return; } - this.slide = slide; - if (slide.isInProgress() && showProgress) { + if (!hideControls && slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_STARTED) { getProgressWheel().spin(); getProgressWheel().setVisibility(VISIBLE); + downloadButton.setVisibility(GONE); + } else if (!hideControls && slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING || + slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_FAILED) + { + hideProgressWheel(); + downloadButton.setVisibility(VISIBLE); } else { hideProgressWheel(); + downloadButton.setVisibility(GONE); } + + this.slide = slide; buildGlideRequest(slide, masterSecret).into(image); setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide)); + downloadButton.setOnClickListener(new ThumbnailClickDispatcher(downloadClickListener, slide)); } public void setThumbnailClickListener(ThumbnailClickListener listener) { @@ -181,15 +195,17 @@ public class ThumbnailView extends FrameLayout { getRemoveButton().setOnClickListener(listener); } + public void setDownloadClickListener(ThumbnailClickListener listener) { + this.downloadClickListener = listener; + } + public void clear() { if (isContextValid()) Glide.clear(this); } - public void setShowProgress(boolean showProgress) { - this.showProgress = showProgress; - if (progress != null && progress.getVisibility() == View.VISIBLE && !showProgress) { - animateOutProgress(); - } + public void hideControls(boolean hideControls) { + this.hideControls = hideControls; + if (hideControls) hideProgressWheel(); } @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) @@ -202,7 +218,6 @@ public class ThumbnailView extends FrameLayout { private GenericRequestBuilder buildGlideRequest(@NonNull Slide slide, @Nullable MasterSecret masterSecret) { - Log.w(TAG, "slide type " + slide.getContentType()); final GenericRequestBuilder builder; if (slide.getThumbnailUri() != null) { builder = buildThumbnailGlideRequest(slide, masterSecret); @@ -210,7 +225,7 @@ public class ThumbnailView extends FrameLayout { builder = buildPlaceholderGlideRequest(slide); } - if (slide.isInProgress() && showProgress) { + if (slide.isInProgress() && !hideControls) { return builder; } else { return builder.error(R.drawable.ic_missing_thumbnail_picture); diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 8a269b86f5..37349524e4 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -752,7 +752,7 @@ public class MmsDatabase extends MessagingDatabase { if (sendRequest.getBody() != null) { for (int i = 0; i < sendRequest.getBody().getPartsNum(); i++) { - sendRequest.getBody().getPart(i).setInProgress(true); + sendRequest.getBody().getPart(i).setTransferProgress(PartDatabase.TRANSFER_PROGRESS_STARTED); } } diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index d523e694e9..17d40fc609 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -23,14 +23,18 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.net.Uri; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirement; +import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData; @@ -43,12 +47,14 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import de.greenrobot.event.EventBus; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.PduBody; @@ -72,12 +78,17 @@ public class PartDatabase extends Database { 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 TRANSFER_STATE = "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"; + public static final int TRANSFER_PROGRESS_DONE = 0; + public static final int TRANSFER_PROGRESS_STARTED = 1; + public static final int TRANSFER_PROGRESS_AUTO_PENDING = 2; + public static final int TRANSFER_PROGRESS_FAILED = 3; + private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + @@ -86,12 +97,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, " + - IN_PROGRESS + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + + TRANSFER_STATE + " 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 + " (" + IN_PROGRESS + ");", + "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + TRANSFER_STATE + ");", }; private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", " @@ -127,7 +138,7 @@ public class PartDatabase extends Database { SQLiteDatabase database = databaseHelper.getWritableDatabase(); part.setContentDisposition(new byte[0]); - part.setInProgress(false); + part.setTransferProgress(TRANSFER_PROGRESS_FAILED); ContentValues values = getContentValuesForPart(part); @@ -223,6 +234,7 @@ public class PartDatabase extends Database { } void insertParts(MasterSecretUnion masterSecret, long mmsId, PduBody body) throws MmsException { + Log.w(TAG, "insertParts(" + body.getPartsNum() + ")"); for (int i=0;i getPendingParts() { + final SQLiteDatabase database = databaseHelper.getReadableDatabase(); + final List parts = new LinkedList<>(); + + Cursor cursor = null; + try { + cursor = database.query(TABLE_NAME, null, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null); + while (cursor != null && cursor.moveToNext()) { + parts.add(getPart(cursor)); + } + } finally { + if (cursor != null) cursor.close(); + } + + return parts; + } + private PartId insertPart(MasterSecretUnion masterSecret, PduPart part, long mmsId, Bitmap thumbnail) throws MmsException { Log.w(TAG, "inserting part to mms " + mmsId); SQLiteDatabase database = databaseHelper.getWritableDatabase(); @@ -472,7 +501,7 @@ public class PartDatabase extends Database { Pair partData = writePartData(masterSecret, part, data); part.setContentDisposition(new byte[0]); - part.setInProgress(false); + part.setTransferProgress(TRANSFER_PROGRESS_DONE); ContentValues values = getContentValuesForPart(part); @@ -492,13 +521,23 @@ public class PartDatabase extends Database { ContentValues values = new ContentValues(1); SQLiteDatabase database = databaseHelper.getWritableDatabase(); - part.setInProgress(false); - values.put(IN_PROGRESS, false); + part.setTransferProgress(TRANSFER_PROGRESS_DONE); + values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings()); notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); } + public void setTransferState(long messageId, @NonNull PartId partId, int transferState) { + final ContentValues values = new ContentValues(1); + final SQLiteDatabase database = databaseHelper.getWritableDatabase(); + + values.put(TRANSFER_STATE, transferState); + database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings()); + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + ApplicationContext.getInstance(context).notifyMediaControlEvent(); + } + public void updatePartData(MasterSecret masterSecret, PduPart part, InputStream data) throws MmsException { diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 72293e5670..a6b45afc00 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -12,6 +12,7 @@ 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.jobs.requirements.MediaNetworkRequirement; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobParameters; @@ -26,7 +27,6 @@ import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.List; import javax.inject.Inject; @@ -35,50 +35,49 @@ import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.PduPart; public class AttachmentDownloadJob extends MasterSecretJob implements InjectableType { - - private static final String TAG = AttachmentDownloadJob.class.getSimpleName(); + private static final long serialVersionUID = 1L; + private static final String TAG = AttachmentDownloadJob.class.getSimpleName(); @Inject transient TextSecureMessageReceiver messageReceiver; private final long messageId; + private final long partRowId; + private final long partUniqueId; - public AttachmentDownloadJob(Context context, long messageId) { + public AttachmentDownloadJob(Context context, long messageId, PartId partId) { super(context, JobParameters.newBuilder() + .withGroupId(AttachmentDownloadJob.class.getCanonicalName()) .withRequirement(new MasterSecretRequirement(context)) .withRequirement(new NetworkRequirement(context)) + .withRequirement(new MediaNetworkRequirement(context, messageId, partId)) .withPersistence() .create()); - this.messageId = messageId; + this.messageId = messageId; + this.partRowId = partId.getRowId(); + this.partUniqueId = partId.getUniqueId(); } @Override - public void onAdded() {} + public void onAdded() { + } @Override public void onRun(MasterSecret masterSecret) throws IOException { - PartDatabase database = DatabaseFactory.getPartDatabase(context); + final PartId partId = new PartId(partRowId, partUniqueId); + final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId); - Log.w(TAG, "Downloading push parts for: " + messageId); - - List parts = database.getParts(messageId); - - for (PduPart part : parts) { - retrievePart(masterSecret, part, messageId); - Log.w(TAG, "Got part: " + part.getPartId()); - } + Log.w(TAG, "Downloading push part " + partId); + retrievePart(masterSecret, part, messageId); MessageNotifier.updateNotification(context, masterSecret); } @Override public void onCanceled() { - PartDatabase database = DatabaseFactory.getPartDatabase(context); - List parts = database.getParts(messageId); - - for (PduPart part : parts) { - markFailed(messageId, part, part.getPartId()); - } + final PartId partId = new PartId(partRowId, partUniqueId); + final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId); + markFailed(messageId, part, part.getPartId()); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 5aef986599..6fefba4229 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -58,9 +58,11 @@ import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMess import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage; import org.whispersystems.textsecure.api.push.TextSecureAddress; +import java.util.List; import java.util.concurrent.TimeUnit; import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.PduPart; public class PushDecryptJob extends ContextJob { @@ -252,11 +254,14 @@ public class PushDecryptJob extends ContextJob { message.getGroupInfo(), message.getAttachments()); - Pair messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + Pair messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new AttachmentDownloadJob(context, messageAndThreadId.first)); + List parts = DatabaseFactory.getPartDatabase(context).getParts(messageAndThreadId.first); + for (PduPart part : parts) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new AttachmentDownloadJob(context, messageAndThreadId.first, part.getPartId())); + } if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); @@ -284,9 +289,11 @@ public class PushDecryptJob extends ContextJob { database.markAsSent(messageId, "push".getBytes(), 0); database.markAsPush(messageId); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new AttachmentDownloadJob(context, messageId)); + for (PduPart part : DatabaseFactory.getPartDatabase(context).getParts(messageId)) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new AttachmentDownloadJob(context, messageId, part.getPartId())); + } if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java new file mode 100644 index 0000000000..146ac2bfb1 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java @@ -0,0 +1,99 @@ +package org.thoughtcrime.securesms.jobs.requirements; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.support.annotation.NonNull; +import android.util.Log; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.database.PartDatabase.PartId; +import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.dependencies.ContextDependent; +import org.whispersystems.jobqueue.requirements.Requirement; + +import java.util.Collections; +import java.util.Set; + +import ws.com.google.android.mms.pdu.PduPart; + +public class MediaNetworkRequirement implements Requirement, ContextDependent { + private static final long serialVersionUID = 0L; + private static final String TAG = MediaNetworkRequirement.class.getSimpleName(); + + private transient Context context; + + private final long messageId; + private final long partRowId; + private final long partUniqueId; + + public MediaNetworkRequirement(Context context, long messageId, PartId partId) { + this.context = context; + this.messageId = messageId; + this.partRowId = partId.getRowId(); + this.partUniqueId = partId.getUniqueId(); + } + + @Override public void setContext(Context context) { + this.context = context; + } + + private NetworkInfo getNetworkInfo() { + return ServiceUtil.getConnectivityManager(context).getActiveNetworkInfo(); + } + + public boolean isConnectedWifi() { + final NetworkInfo info = getNetworkInfo(); + return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; + } + + public boolean isConnectedMobile() { + final NetworkInfo info = getNetworkInfo(); + return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE; + } + + public boolean isConnectedRoaming() { + final NetworkInfo info = getNetworkInfo(); + return info != null && info.isConnected() && info.isRoaming() && info.getType() == ConnectivityManager.TYPE_MOBILE; + } + + private @NonNull Set getAllowedAutoDownloadTypes() { + if (isConnectedWifi()) { + return TextSecurePreferences.getWifiMediaDownloadAllowed(context); + } else if (isConnectedRoaming()) { + return TextSecurePreferences.getRoamingMediaDownloadAllowed(context); + } else if (isConnectedMobile()) { + return TextSecurePreferences.getMobileMediaDownloadAllowed(context); + } else { + return Collections.emptySet(); + } + } + + @Override + public boolean isPresent() { + final PartId partId = new PartId(partRowId, partUniqueId); + final PartDatabase db = DatabaseFactory.getPartDatabase(context); + final PduPart part = db.getPart(partId); + if (part == null) { + Log.w(TAG, "part was null"); + return false; + } + + Log.w(TAG, "part transfer progress is " + part.getTransferProgress()); + switch (part.getTransferProgress()) { + case PartDatabase.TRANSFER_PROGRESS_STARTED: + return true; + case PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING: + final Set allowedTypes = getAllowedAutoDownloadTypes(); + final boolean isAllowed = allowedTypes.contains(MediaUtil.getDiscreteMimeType(part)); + + if (isAllowed) db.setTransferState(messageId, partId, PartDatabase.TRANSFER_PROGRESS_STARTED); + return isAllowed; + default: + return false; + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java new file mode 100644 index 0000000000..0019c461c6 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java @@ -0,0 +1,18 @@ +package org.thoughtcrime.securesms.jobs.requirements; + +import org.whispersystems.jobqueue.requirements.RequirementListener; +import org.whispersystems.jobqueue.requirements.RequirementProvider; + +public class MediaNetworkRequirementProvider implements RequirementProvider { + + private RequirementListener listener; + + public void notifyMediaControlEvent() { + if (listener != null) listener.onRequirementStatusChanged(); + } + + @Override + public void setListener(RequirementListener listener) { + this.listener = listener; + } +} diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 4659b647eb..f5862e506e 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.mms; import android.text.TextUtils; +import android.util.Log; -import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.crypto.MediaKey; -import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.database.PartDatabase; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libaxolotl.util.guava.Optional; @@ -83,7 +81,7 @@ public class IncomingMediaMessage { media.setName(Util.toIsoBytes(relay.get())); } - media.setInProgress(true); + media.setTransferProgress(PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING); this.body.addPart(media); } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index 6eb5f14648..7f52885292 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -78,6 +78,10 @@ public abstract class Slide { return part.isInProgress(); } + public long getTransferProgress() { + return part.getTransferProgress(); + } + public @DrawableRes int getPlaceholderRes(Theme theme) { throw new AssertionError("getPlaceholderRes() called for non-drawable slide"); } @@ -111,7 +115,7 @@ public abstract class Slide { this.hasImage() == that.hasImage() && this.hasVideo() == that.hasVideo() && this.isDraft() == that.isDraft() && - this.isInProgress() == that.isInProgress() && + this.getTransferProgress() == that.getTransferProgress() && Util.equals(this.getUri(), that.getUri()) && Util.equals(this.getThumbnailUri(), that.getThumbnailUri()); } @@ -119,7 +123,7 @@ public abstract class Slide { @Override public int hashCode() { return Util.hashCode(getContentType(), hasAudio(), hasImage(), - hasVideo(), isDraft(), getUri(), getThumbnailUri()); + hasVideo(), isDraft(), getUri(), getThumbnailUri(), getTransferProgress()); } diff --git a/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java new file mode 100644 index 0000000000..3def50cfac --- /dev/null +++ b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java @@ -0,0 +1,143 @@ +package org.thoughtcrime.securesms.preferences; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.MultiSelectListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.support.v4.preference.PreferenceFragment; +import android.text.TextUtils; +import android.util.Log; + +import com.afollestad.materialdialogs.AlertDialogWrapper; + +import org.thoughtcrime.securesms.ApplicationPreferencesActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Trimmer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ChatsPreferenceFragment extends PreferenceFragment { + private static final String TAG = ChatsPreferenceFragment.class.getSimpleName(); + + @Override + public void onCreate(Bundle paramBundle) { + super.onCreate(paramBundle); + addPreferencesFromResource(R.xml.preferences_chats); + + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF) + .setOnPreferenceChangeListener(new MediaDownloadChangeListener()); + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF) + .setOnPreferenceChangeListener(new MediaDownloadChangeListener()); + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF) + .setOnPreferenceChangeListener(new MediaDownloadChangeListener()); + + findPreference(TextSecurePreferences.THREAD_TRIM_NOW) + .setOnPreferenceClickListener(new TrimNowClickListener()); + findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH) + .setOnPreferenceChangeListener(new TrimLengthValidationListener()); + + } + + @Override + public void onResume() { + super.onResume(); + ((ApplicationPreferencesActivity)getActivity()).getSupportActionBar().setTitle(R.string.preferences__chats); + setMediaDownloadSummaries(); + } + + private void setMediaDownloadSummaries() { + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF) + .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getMobileMediaDownloadAllowed(getActivity()))); + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF) + .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getWifiMediaDownloadAllowed(getActivity()))); + findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF) + .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getRoamingMediaDownloadAllowed(getActivity()))); + } + + private CharSequence getSummaryForMediaPreference(Set allowedNetworks) { + String[] keys = getResources().getStringArray(R.array.pref_media_download_entries); + String[] values = getResources().getStringArray(R.array.pref_media_download_values); + List outValues = new ArrayList<>(allowedNetworks.size()); + + for (int i=0; i < keys.length; i++) { + if (allowedNetworks.contains(keys[i])) outValues.add(values[i]); + } + + return outValues.isEmpty() ? getResources().getString(R.string.preferences__none) + : TextUtils.join(", ", outValues); + } + + private class TrimNowClickListener implements Preference.OnPreferenceClickListener { + @Override + public boolean onPreferenceClick(Preference preference) { + final int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(getActivity()); + AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity()); + builder.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now); + builder.setMessage(getString(R.string.ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages, + threadLengthLimit)); + builder.setPositiveButton(R.string.ApplicationPreferencesActivity_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Trimmer.trimAllThreads(getActivity(), threadLengthLimit); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + + return true; + } + } + + private class MediaDownloadChangeListener implements OnPreferenceChangeListener { + @SuppressWarnings("unchecked") + @Override public boolean onPreferenceChange(Preference preference, Object newValue) { + Log.w(TAG, "onPreferenceChange"); + preference.setSummary(getSummaryForMediaPreference((Set)newValue)); + return true; + } + } + + private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener { + + public TrimLengthValidationListener() { + EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH); + preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, preference.getText())); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue == null || ((String)newValue).trim().length() == 0) { + return false; + } + + try { + Integer.parseInt((String)newValue); + } catch (NumberFormatException nfe) { + Log.w(TAG, nfe); + return false; + } + + if (Integer.parseInt((String)newValue) < 1) { + return false; + } + + preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, newValue)); + return true; + } + } + + public static CharSequence getSummary(Context context) { + return null; + } +} diff --git a/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java deleted file mode 100644 index 616ba275b6..0000000000 --- a/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.thoughtcrime.securesms.preferences; - -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.support.v4.preference.PreferenceFragment; -import android.util.Log; - -import com.afollestad.materialdialogs.AlertDialogWrapper; - -import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Trimmer; - -public class StoragePreferenceFragment extends PreferenceFragment { - private static final String TAG = StoragePreferenceFragment.class.getSimpleName(); - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - addPreferencesFromResource(R.xml.preferences_storage); - - this.findPreference(TextSecurePreferences.THREAD_TRIM_NOW) - .setOnPreferenceClickListener(new TrimNowClickListener()); - this.findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH) - .setOnPreferenceChangeListener(new TrimLengthValidationListener()); - } - - @Override - public void onResume() { - super.onResume(); - ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__delete_old_messages); - } - - private class TrimNowClickListener implements Preference.OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(Preference preference) { - final int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(getActivity()); - AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity()); - builder.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now); - builder.setMessage(getString(R.string.ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages, - threadLengthLimit)); - builder.setPositiveButton(R.string.ApplicationPreferencesActivity_delete, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Trimmer.trimAllThreads(getActivity(), threadLengthLimit); - } - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - - return true; - } - } - - private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener { - - public TrimLengthValidationListener() { - EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH); - preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, preference.getText())); - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (newValue == null || ((String)newValue).trim().length() == 0) { - return false; - } - - try { - Integer.parseInt((String)newValue); - } catch (NumberFormatException nfe) { - Log.w(TAG, nfe); - return false; - } - - if (Integer.parseInt((String)newValue) < 1) { - return false; - } - - preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, newValue)); - return true; - } - } - - public static CharSequence getSummary(Context context) { - final int onCapsResId = R.string.ApplicationPreferencesActivity_On; - final int offCapsResId = R.string.ApplicationPreferencesActivity_Off; - - return context.getString(TextSecurePreferences.isThreadLengthTrimmingEnabled(context) ? onCapsResId : offCapsResId); - } -} diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java index d63ad362db..b04d4f522e 100644 --- a/src/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.util; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; import android.webkit.MimeTypeMap; @@ -108,6 +110,11 @@ public class MediaUtil { return ContentType.isVideoType(Util.toIsoString(part.getContentType())); } + public static @Nullable String getDiscreteMimeType(@NonNull PduPart part) { + final String[] sections = (Util.toIsoString(part.getContentType()).split("/", 2)); + return sections.length > 1 ? sections[0] : null; + } + public static class ThumbnailData { Bitmap bitmap; float aspectRatio; diff --git a/src/org/thoughtcrime/securesms/util/ServiceUtil.java b/src/org/thoughtcrime/securesms/util/ServiceUtil.java index 049433d437..d1cfdae7a8 100644 --- a/src/org/thoughtcrime/securesms/util/ServiceUtil.java +++ b/src/org/thoughtcrime/securesms/util/ServiceUtil.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util; import android.app.Activity; import android.content.Context; +import android.net.ConnectivityManager; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; @@ -13,4 +14,8 @@ public class ServiceUtil { public static WindowManager getWindowManager(Context context) { return (WindowManager) context.getSystemService(Activity.WINDOW_SERVICE); } + + public static ConnectivityManager getConnectivityManager(Context context) { + return (ConnectivityManager) context.getSystemService(Activity.CONNECTIVITY_SERVICE); + } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index c5572da592..f94a4efa72 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -4,11 +4,19 @@ import android.content.Context; import android.os.Build; import android.preference.PreferenceManager; import android.provider.Settings; +import android.support.annotation.ArrayRes; +import android.support.annotation.NonNull; import android.util.Log; +import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.preferences.NotificationPrivacyPreference; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; public class TextSecurePreferences { @@ -74,6 +82,10 @@ public class TextSecurePreferences { public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts"; public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy"; + public static final String MEDIA_DOWNLOAD_MOBILE_PREF = "pref_media_download_mobile"; + public static final String MEDIA_DOWNLOAD_WIFI_PREF = "pref_media_download_wifi"; + public static final String MEDIA_DOWNLOAD_ROAMING_PREF = "pref_media_download_roaming"; + public static NotificationPrivacyPreference getNotificationPrivacy(Context context) { return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); } @@ -433,6 +445,25 @@ public class TextSecurePreferences { return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500")); } + public static @NonNull Set getMobileMediaDownloadAllowed(Context context) { + return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_MOBILE_PREF, R.array.pref_media_download_mobile_data_default); + } + + public static @NonNull Set getWifiMediaDownloadAllowed(Context context) { + return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_WIFI_PREF, R.array.pref_media_download_wifi_default); + } + + public static @NonNull Set getRoamingMediaDownloadAllowed(Context context) { + return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_ROAMING_PREF, R.array.pref_media_download_roaming_default); + } + + private static @NonNull Set getMediaDownloadAllowed(Context context, String key, @ArrayRes int defaultValuesRes) { + return getStringSetPreference(context, + key, + new HashSet<>(Arrays.asList(context.getResources().getStringArray(defaultValuesRes)))); + } + + public static void setBooleanPreference(Context context, String key, boolean value) { PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply(); } @@ -468,4 +499,8 @@ public class TextSecurePreferences { private static void setLongPreference(Context context, String key, long value) { PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(key, value).apply(); } + + private static Set getStringSetPreference(Context context, String key, Set defaultValues) { + return PreferenceManager.getDefaultSharedPreferences(context).getStringSet(key, defaultValues); + } } diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index a7ce4ce806..64f96f9dde 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -28,6 +28,7 @@ import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.Looper; import android.provider.Telephony; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.TelephonyManager; import android.text.Spannable; @@ -119,7 +120,7 @@ public class Util { return spanned; } - public static String toIsoString(byte[] bytes) { + public static @NonNull String toIsoString(byte[] bytes) { try { return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); } catch (UnsupportedEncodingException e) { diff --git a/src/ws/com/google/android/mms/pdu/PduPart.java b/src/ws/com/google/android/mms/pdu/PduPart.java index c17c742709..630f232d60 100644 --- a/src/ws/com/google/android/mms/pdu/PduPart.java +++ b/src/ws/com/google/android/mms/pdu/PduPart.java @@ -134,8 +134,9 @@ public class PduPart { private long rowId = -1; private long uniqueId = -1; + private long mmsId = -1; private boolean isEncrypted; - private boolean isInProgress; + private int transferProgress; private long dataSize; private Bitmap thumbnail; @@ -163,13 +164,17 @@ public class PduPart { return this.dataSize; } + public boolean isInProgress() { + return transferProgress != PartDatabase.TRANSFER_PROGRESS_DONE && + transferProgress != PartDatabase.TRANSFER_PROGRESS_FAILED; + } - public void setInProgress(boolean isInProgress) { - this.isInProgress = isInProgress; + public void setTransferProgress(int transferProgress) { + this.transferProgress = transferProgress; } - public boolean isInProgress() { - return isInProgress; + public int getTransferProgress() { + return transferProgress; } /** @@ -475,5 +480,13 @@ public class PduPart { public void setUniqueId(long uniqueId) { this.uniqueId = uniqueId; } + + public long getMmsId() { + return mmsId; + } + + public void setMmsId(long mmsId) { + this.mmsId = mmsId; + } }