Merge branch 'development'

This commit is contained in:
Daniel Gultsch 2015-05-20 03:39:58 +02:00
commit 4f36aa39a4
196 changed files with 2817 additions and 895 deletions

View file

@ -3,7 +3,8 @@ android:
components: components:
- platform-tools - platform-tools
- tools - tools
- build-tools-22.0.1
- build-tools-21.1.2 - build-tools-21.1.2
- build-tools-19.1.0 - build-tools-19.1.0
- android-21 - android-22
- extra-android-m2repository - extra-android-m2repository

View file

@ -1,5 +1,12 @@
###Changelog ###Changelog
####Version 1.4.0
* send button turns into quick action button to offer faster access to take photo, send location or record audio
* visually seperate merged messages
* faster reconnects of failed accounts after network switches
* r/o vcard avatars for contacts
* various bug fixes
####Version 1.3.0 ####Version 1.3.0
* swipe conversations to end them * swipe conversations to end them
* quickly enable / disable account via slider * quickly enable / disable account via slider

View file

@ -67,12 +67,13 @@ run your own XMPP server for you and your friends. These XEP's are:
(In order of appearance) (In order of appearance)
* [Rene Treffer](https://github.com/rtreffer) * [Rene Treffer](https://github.com/rtreffer) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Artreffer+is%3Amerged))
* [Andreas Straub](https://github.com/strb) * [Andreas Straub](https://github.com/strb) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Astrb+is%3Amerged))
* [Alethea Butler](https://github.com/alethea) * [Alethea Butler](https://github.com/alethea) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Aalethea+is%3Amerged))
* [M. Dietrich](https://github.com/emdete) * [M. Dietrich](https://github.com/emdete) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Aemdete+is%3Amerged))
* [betheg](https://github.com/betheg) * [betheg](https://github.com/betheg) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Abetheg+is%3Amerged))
* [Sam Whited](https://github.com/SamWhited) * [Sam Whited](https://github.com/SamWhited) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3ASamWhited+is%3Amerged))
* [BrianBlade](https://github.com/BrianBlade) ([PRs](https://github.com/siacs/Conversations/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3ABrianBlade+is%3Amerged))
#### Logo #### Logo
* [Ilia Rostovtsev](https://github.com/qooob) (Progress) * [Ilia Rostovtsev](https://github.com/qooob) (Progress)

View file

@ -13,7 +13,7 @@
width="95" width="95"
height="95" height="95"
id="Yes_check" id="Yes_check"
inkscape:version="0.48.5 r10040" inkscape:version="0.91 r13725"
sodipodi:docname="ic_received_indicator.svg"> sodipodi:docname="ic_received_indicator.svg">
<metadata <metadata
id="metadata10"> id="metadata10">
@ -23,7 +23,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title> <dc:title />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
@ -36,17 +36,17 @@
guidetolerance="10" guidetolerance="10"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="1233" inkscape:window-width="956"
inkscape:window-height="828" inkscape:window-height="1156"
id="namedview8" id="namedview8"
showgrid="false" showgrid="false"
showguides="true" showguides="true"
inkscape:guide-bbox="true" inkscape:guide-bbox="true"
inkscape:zoom="5.04" inkscape:zoom="5.04"
inkscape:cx="26.829268" inkscape:cx="-4.3215257"
inkscape:cy="37.489149" inkscape:cy="37.489149"
inkscape:window-x="0" inkscape:window-x="2880"
inkscape:window-y="0" inkscape:window-y="20"
inkscape:window-maximized="0" inkscape:window-maximized="0"
inkscape:current-layer="Yes_check" inkscape:current-layer="Yes_check"
fit-margin-top="0" fit-margin-top="0"
@ -69,7 +69,7 @@
</defs> </defs>
<path <path
d="m 2.3894499,61.412131 c 0,0 16.7473651,20.271938 22.3528491,26.154483 3.648598,3.026816 12.878061,3.83429 14.880462,0 1.64903,-2.636163 2.380404,-5.8348 2.991819,-7.931771 C 49.920898,54.575958 72.297563,22.337321 92.321082,10.50894 96.814837,5.2377522 86.327596,3.5063483 77.217442,6.9958109 63.487006,12.254946 34.107717,59.529917 29.270873,69.192545 22.40265,70.841418 12.518762,52.447046 12.518762,52.447046 7.3805037,52.552428 1.8841059,52.071763 2.3894499,61.412131 z" d="m 2.3894499,61.412131 c 0,0 16.7473651,20.271938 22.3528491,26.154483 3.648598,3.026816 12.878061,3.83429 14.880462,0 1.64903,-2.636163 2.380404,-5.8348 2.991819,-7.931771 C 49.920898,54.575958 72.297563,22.337321 92.321082,10.50894 96.814837,5.2377522 86.327596,3.5063483 77.217442,6.9958109 63.487006,12.254946 34.107717,59.529917 29.270873,69.192545 22.40265,70.841418 12.518762,52.447046 12.518762,52.447046 7.3805037,52.552428 1.8841059,52.071763 2.3894499,61.412131 z"
style="fill:#249b25;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.29981154;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" style="fill:#259b24;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29981154;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="check" id="check"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscsccc" /> sodipodi:nodetypes="cccscsccc" />

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_cancel_away.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="507"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="549"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z"
id="path4"
style="fill:#ff9800;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_cancel_dnd.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="507"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="549"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z"
id="path4"
style="fill:#f44336;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_cancel_offline.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z"
id="path4"
style="fill:#000000;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_cancel_online.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="507"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="549"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z"
id="path4"
style="fill:#259b24;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_location_away.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="0.91525424"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path4"
style="fill:#ff9800;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_location_dnd.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path4"
style="fill:#f44336;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_location_offline.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path4"
style="fill:#000000;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_location_online.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path4"
style="stroke:none;stroke-opacity:0;fill:#259b24;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_photo_away.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="567"
id="namedview8"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="960"
inkscape:window-y="609"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<circle
cx="24"
cy="24"
r="6.4"
id="circle4"
style="fill-opacity:0.627451;fill:#ff9800" />
<path
d="M18 4l-3.66 4H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4h-6.34L30 4H18zm6 30c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10z"
id="path6"
style="fill:#ff9800;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

60
art/ic_send_photo_dnd.svg Normal file
View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_photo_dnd.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="567"
id="namedview8"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="960"
inkscape:window-y="609"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<circle
cx="24"
cy="24"
r="6.4"
id="circle4"
style="fill:#f44336;fill-opacity:0.627451" />
<path
d="M18 4l-3.66 4H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4h-6.34L30 4H18zm6 30c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10z"
id="path6"
style="fill:#f44336;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_photo_offline.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="567"
id="namedview8"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="960"
inkscape:window-y="609"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<circle
cx="24"
cy="24"
r="6.4"
id="circle4"
style="fill:#000000;fill-opacity:0.627451" />
<path
d="M18 4l-3.66 4H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4h-6.34L30 4H18zm6 30c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10z"
id="path6"
style="fill:#000000;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_photo_online.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="567"
id="namedview8"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-8.3389831"
inkscape:cy="24"
inkscape:window-x="960"
inkscape:window-y="609"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<circle
cx="24"
cy="24"
r="6.4"
id="circle4"
style="fill:#259b24;fill-opacity:0.627451" />
<path
d="M18 4l-3.66 4H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4h-6.34L30 4H18zm6 30c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10z"
id="path6"
style="fill:#259b24;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

69
art/ic_send_text_away.svg Normal file
View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3621"
version="1.1"
inkscape:version="0.91 r13725"
width="96"
height="96"
sodipodi:docname="ic_send_text_away.svg"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-xxhdpi/ic_action_send_now_online.png"
inkscape:export-xdpi="154.28572"
inkscape:export-ydpi="154.28572">
<metadata
id="metadata3627">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3625" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="744"
inkscape:window-height="1156"
id="namedview3623"
showgrid="true"
showguides="true"
inkscape:zoom="8"
inkscape:cx="55.595803"
inkscape:cy="56.761328"
inkscape:window-x="3092"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg3621">
<inkscape:grid
type="xygrid"
id="grid3631" />
</sodipodi:namedview>
<path
style="fill:#ff9800;fill-opacity:0.627451;stroke:none"
d="M 3.887575,4.1549246 90.999747,47.676331 3.887575,91.286663 13.203552,52.344101 63.012683,47.720794 13.203552,43.008558 Z"
id="path3633"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-mdpi/ic_action_send_now_dnd.png"
inkscape:export-xdpi="51.42857"
inkscape:export-ydpi="51.42857" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

69
art/ic_send_text_dnd.svg Normal file
View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3621"
version="1.1"
inkscape:version="0.91 r13725"
width="96"
height="96"
sodipodi:docname="ic_send_text_dnd.svg"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-xxhdpi/ic_action_send_now_online.png"
inkscape:export-xdpi="154.28572"
inkscape:export-ydpi="154.28572">
<metadata
id="metadata3627">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3625" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview3623"
showgrid="true"
showguides="true"
inkscape:zoom="8"
inkscape:cx="49.908303"
inkscape:cy="56.761328"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg3621">
<inkscape:grid
type="xygrid"
id="grid3631" />
</sodipodi:namedview>
<path
style="fill:#f44336;fill-opacity:0.627451;stroke:none"
d="M 3.887575,4.1549246 90.999747,47.676331 3.887575,91.286663 13.203552,52.344101 63.012683,47.720794 13.203552,43.008558 Z"
id="path3633"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-mdpi/ic_action_send_now_dnd.png"
inkscape:export-xdpi="51.42857"
inkscape:export-ydpi="51.42857" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3621"
version="1.1"
inkscape:version="0.91 r13725"
width="96"
height="96"
sodipodi:docname="ic_send_text_offline.svg"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-xxhdpi/ic_action_send_now_online.png"
inkscape:export-xdpi="154.28572"
inkscape:export-ydpi="154.28572">
<metadata
id="metadata3627">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3625" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview3623"
showgrid="true"
showguides="true"
inkscape:zoom="8"
inkscape:cx="50.158303"
inkscape:cy="56.761328"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg3621">
<inkscape:grid
type="xygrid"
id="grid3631" />
</sodipodi:namedview>
<path
style="fill:#000000;fill-opacity:0.627451;stroke:none"
d="M 3.887575,4.1549246 90.999747,47.676331 3.887575,91.286663 13.203552,52.344101 63.012683,47.720794 13.203552,43.008558 Z"
id="path3633"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-mdpi/ic_action_send_now_dnd.png"
inkscape:export-xdpi="51.42857"
inkscape:export-ydpi="51.42857" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -11,7 +11,7 @@
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3621" id="svg3621"
version="1.1" version="1.1"
inkscape:version="0.48.4 r9939" inkscape:version="0.91 r13725"
width="96" width="96"
height="96" height="96"
sodipodi:docname="ic_action_send_now.svg" sodipodi:docname="ic_action_send_now.svg"
@ -26,7 +26,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title> <dc:title />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
@ -41,16 +41,16 @@
guidetolerance="10" guidetolerance="10"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="1916" inkscape:window-width="1920"
inkscape:window-height="1161" inkscape:window-height="1200"
id="namedview3623" id="namedview3623"
showgrid="true" showgrid="true"
showguides="true" showguides="true"
inkscape:zoom="1" inkscape:zoom="8"
inkscape:cx="47.28873" inkscape:cx="69.783303"
inkscape:cy="43.262706" inkscape:cy="56.761328"
inkscape:window-x="0" inkscape:window-x="1920"
inkscape:window-y="18" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="0"
inkscape:current-layer="svg3621"> inkscape:current-layer="svg3621">
<inkscape:grid <inkscape:grid
@ -58,8 +58,8 @@
id="grid3631" /> id="grid3631" />
</sodipodi:namedview> </sodipodi:namedview>
<path <path
style="fill:#e51c28;fill-opacity:0.627451;stroke:none" style="fill:#259b24;fill-opacity:0.62745098;stroke:none"
d="M 20.012575,21.028577 76,49 20.012575,77.028577 26,52 58.012575,49.028577 26,46 z" d="M 3.887575,4.1549246 90.999747,47.676331 3.887575,91.286663 13.203552,52.344101 63.012683,47.720794 13.203552,43.008558 Z"
id="path3633" id="path3633"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" sodipodi:nodetypes="ccccccc"

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_voice_away.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 30c3.31 0 5.98-2.69 5.98-6L30 12c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V44h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z"
id="path4"
style="fill:#ff9800;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

54
art/ic_send_voice_dnd.svg Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_voice_dnd.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 30c3.31 0 5.98-2.69 5.98-6L30 12c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V44h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z"
id="path4"
style="fill:#f44336;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_voice_offline.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 30c3.31 0 5.98-2.69 5.98-6L30 12c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V44h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z"
id="path4"
style="fill:#000000;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_send_voice_online.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-7.9322034"
inkscape:cy="24"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 30c3.31 0 5.98-2.69 5.98-6L30 12c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V44h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z"
id="path4"
style="fill:#259b24;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -10,6 +10,26 @@ images = {
'conversations_baloon.svg' => ['ic_launcher', 48], 'conversations_baloon.svg' => ['ic_launcher', 48],
'conversations_mono.svg' => ['ic_notification', 24], 'conversations_mono.svg' => ['ic_notification', 24],
'ic_received_indicator.svg' => ['ic_received_indicator', 12], 'ic_received_indicator.svg' => ['ic_received_indicator', 12],
'ic_send_text_offline.svg' => ['ic_send_text_offline', 36],
'ic_send_text_online.svg' => ['ic_send_text_online', 36],
'ic_send_text_away.svg' => ['ic_send_text_away', 36],
'ic_send_text_dnd.svg' => ['ic_send_text_dnd', 36],
'ic_send_photo_online.svg' => ['ic_send_photo_online', 36],
'ic_send_photo_offline.svg' => ['ic_send_photo_offline', 36],
'ic_send_photo_away.svg' => ['ic_send_photo_away', 36],
'ic_send_photo_dnd.svg' => ['ic_send_photo_dnd', 36],
'ic_send_location_online.svg' => ['ic_send_location_online', 36],
'ic_send_location_offline.svg' => ['ic_send_location_offline', 36],
'ic_send_location_away.svg' => ['ic_send_location_away', 36],
'ic_send_location_dnd.svg' => ['ic_send_location_dnd', 36],
'ic_send_voice_online.svg' => ['ic_send_voice_online', 36],
'ic_send_voice_offline.svg' => ['ic_send_voice_offline', 36],
'ic_send_voice_away.svg' => ['ic_send_voice_away', 36],
'ic_send_voice_dnd.svg' => ['ic_send_voice_dnd', 36],
'ic_send_cancel_online.svg' => ['ic_send_cancel_online', 36],
'ic_send_cancel_offline.svg' => ['ic_send_cancel_offline', 36],
'ic_send_cancel_away.svg' => ['ic_send_cancel_away', 36],
'ic_send_cancel_dnd.svg' => ['ic_send_cancel_dnd', 36],
} }
images.each do |source, result| images.each do |source, result|
resolutions.each do |name, factor| resolutions.each do |name, factor|

View file

@ -38,14 +38,14 @@ dependencies {
} }
android { android {
compileSdkVersion 21 compileSdkVersion 22
buildToolsVersion "21.1.2" buildToolsVersion "22.0.1"
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 21 targetSdkVersion 21
versionCode 61 versionCode 66
versionName "1.3.1" versionName "1.4.0"
} }
compileOptions { compileOptions {

View file

@ -11,6 +11,7 @@ public final class Config {
public static final int PING_MAX_INTERVAL = 300; public static final int PING_MAX_INTERVAL = 300;
public static final int PING_MIN_INTERVAL = 30; public static final int PING_MIN_INTERVAL = 30;
public static final int PING_TIMEOUT = 10; public static final int PING_TIMEOUT = 10;
public static final int SOCKET_TIMEOUT = 15;
public static final int CONNECT_TIMEOUT = 90; public static final int CONNECT_TIMEOUT = 90;
public static final int CARBON_GRACE_PERIOD = 60; public static final int CARBON_GRACE_PERIOD = 60;
public static final int MINI_GRACE_PERIOD = 750; public static final int MINI_GRACE_PERIOD = 750;
@ -28,7 +29,8 @@ public final class Config {
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts
public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;

View file

@ -15,6 +15,7 @@ import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
public class Contact implements ListItem, Blockable { public class Contact implements ListItem, Blockable {
public static final String TABLENAME = "contacts"; public static final String TABLENAME = "contacts";
@ -40,11 +41,11 @@ public class Contact implements ListItem, Blockable {
protected int subscription = 0; protected int subscription = 0;
protected String systemAccount; protected String systemAccount;
protected String photoUri; protected String photoUri;
protected String avatar;
protected JSONObject keys = new JSONObject(); protected JSONObject keys = new JSONObject();
protected JSONArray groups = new JSONArray(); protected JSONArray groups = new JSONArray();
protected Presences presences = new Presences(); protected Presences presences = new Presences();
protected Account account; protected Account account;
protected Avatar avatar;
public Contact(final String account, final String systemName, final String serverName, public Contact(final String account, final String systemName, final String serverName,
final Jid jid, final int subscription, final String photoUri, final Jid jid, final int subscription, final String photoUri,
@ -61,7 +62,11 @@ public class Contact implements ListItem, Blockable {
} catch (JSONException e) { } catch (JSONException e) {
this.keys = new JSONObject(); this.keys = new JSONObject();
} }
this.avatar = avatar; if (avatar != null) {
this.avatar = new Avatar();
this.avatar.sha1sum = avatar;
this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
}
try { try {
this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
} catch (JSONException e) { } catch (JSONException e) {
@ -135,10 +140,10 @@ public class Contact implements ListItem, Blockable {
tags.add(new Tag("away", 0xffff9800)); tags.add(new Tag("away", 0xffff9800));
break; break;
case Presences.XA: case Presences.XA:
tags.add(new Tag("not available", 0xffe51c23)); tags.add(new Tag("not available", 0xfff44336));
break; break;
case Presences.DND: case Presences.DND:
tags.add(new Tag("dnd", 0xffe51c23)); tags.add(new Tag("dnd", 0xfff44336));
break; break;
} }
if (isBlocked()) { if (isBlocked()) {
@ -187,7 +192,7 @@ public class Contact implements ListItem, Blockable {
values.put(SYSTEMACCOUNT, systemAccount); values.put(SYSTEMACCOUNT, systemAccount);
values.put(PHOTOURI, photoUri); values.put(PHOTOURI, photoUri);
values.put(KEYS, keys.toString()); values.put(KEYS, keys.toString());
values.put(AVATAR, avatar); values.put(AVATAR, avatar == null ? null : avatar.getFilename());
values.put(LAST_PRESENCE, lastseen.presence); values.put(LAST_PRESENCE, lastseen.presence);
values.put(LAST_TIME, lastseen.time); values.put(LAST_TIME, lastseen.time);
values.put(GROUPS, groups.toString()); values.put(GROUPS, groups.toString());
@ -411,17 +416,20 @@ public class Contact implements ListItem, Blockable {
return getJid().toDomainJid(); return getJid().toDomainJid();
} }
public boolean setAvatar(String filename) { public boolean setAvatar(Avatar avatar) {
if (this.avatar != null && this.avatar.equals(filename)) { if (this.avatar != null && this.avatar.equals(avatar)) {
return false; return false;
} else { } else {
this.avatar = filename; if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
return false;
}
this.avatar = avatar;
return true; return true;
} }
} }
public String getAvatar() { public String getAvatar() {
return this.avatar; return avatar == null ? null : avatar.getFilename();
} }
public boolean deleteOtrFingerprint(String fingerprint) { public boolean deleteOtrFingerprint(String fingerprint) {
@ -478,6 +486,10 @@ public class Contact implements ListItem, Blockable {
} }
} }
public boolean isSelf() {
return account.getJid().toBareJid().equals(getJid().toBareJid());
}
public static class Lastseen { public static class Lastseen {
public long time; public long time;
public String presence; public String presence;

View file

@ -16,6 +16,7 @@ import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.List; import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
@ -218,6 +219,11 @@ public class Conversation extends AbstractEntity implements Blockable {
messages.clear(); messages.clear();
messages.addAll(this.messages); messages.addAll(this.messages);
} }
for(Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) {
if (iterator.next().wasMergedIntoPrevious()) {
iterator.remove();
}
}
} }
@Override @Override

View file

@ -9,6 +9,7 @@ import java.util.Arrays;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
@ -16,6 +17,8 @@ public class Message extends AbstractEntity {
public static final String TABLENAME = "messages"; public static final String TABLENAME = "messages";
public static final String MERGE_SEPARATOR = "\u200B\n\n";
public static final int STATUS_RECEIVED = 0; public static final int STATUS_RECEIVED = 0;
public static final int STATUS_UNSEND = 1; public static final int STATUS_UNSEND = 1;
public static final int STATUS_SEND = 2; public static final int STATUS_SEND = 2;
@ -95,9 +98,9 @@ public class Message extends AbstractEntity {
} }
private Message(final String uuid, final String conversationUUid, final Jid counterpart, private Message(final String uuid, final String conversationUUid, final Jid counterpart,
final Jid trueCounterpart, final String body, final long timeSent, final Jid trueCounterpart, final String body, final long timeSent,
final int encryption, final int status, final int type, final String remoteMsgId, final int encryption, final int status, final int type, final String remoteMsgId,
final String relativeFilePath, final String serverMsgId) { final String relativeFilePath, final String serverMsgId) {
this.uuid = uuid; this.uuid = uuid;
this.conversationUuid = conversationUUid; this.conversationUuid = conversationUUid;
this.counterpart = counterpart; this.counterpart = counterpart;
@ -179,7 +182,7 @@ public class Message extends AbstractEntity {
values.put(TYPE, type); values.put(TYPE, type);
values.put(REMOTE_MSG_ID, remoteMsgId); values.put(REMOTE_MSG_ID, remoteMsgId);
values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(RELATIVE_FILE_PATH, relativeFilePath);
values.put(SERVER_MSG_ID,serverMsgId); values.put(SERVER_MSG_ID, serverMsgId);
return values; return values;
} }
@ -211,7 +214,7 @@ public class Message extends AbstractEntity {
return null; return null;
} else { } else {
return this.conversation.getAccount().getRoster() return this.conversation.getAccount().getRoster()
.getContactFromRoster(this.trueCounterpart); .getContactFromRoster(this.trueCounterpart);
} }
} }
} }
@ -359,41 +362,43 @@ public class Message extends AbstractEntity {
public boolean mergeable(final Message message) { public boolean mergeable(final Message message) {
return message != null && return message != null &&
(message.getType() == Message.TYPE_TEXT && (message.getType() == Message.TYPE_TEXT &&
this.getDownloadable() == null && this.getDownloadable() == null &&
message.getDownloadable() == null && message.getDownloadable() == null &&
message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_PGP &&
this.getType() == message.getType() && this.getType() == message.getType() &&
//this.getStatus() == message.getStatus() && //this.getStatus() == message.getStatus() &&
isStatusMergeable(this.getStatus(),message.getStatus()) && isStatusMergeable(this.getStatus(), message.getStatus()) &&
this.getEncryption() == message.getEncryption() && this.getEncryption() == message.getEncryption() &&
this.getCounterpart() != null && this.getCounterpart() != null &&
this.getCounterpart().equals(message.getCounterpart()) && this.getCounterpart().equals(message.getCounterpart()) &&
(message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
!GeoHelper.isGeoUri(message.getBody()) && !GeoHelper.isGeoUri(message.getBody()) &&
!GeoHelper.isGeoUri(this.body) && !GeoHelper.isGeoUri(this.body) &&
!message.bodyContainsDownloadable() && !message.bodyContainsDownloadable() &&
!this.bodyContainsDownloadable() && !this.bodyContainsDownloadable() &&
!message.getBody().startsWith(ME_COMMAND) && !message.getBody().startsWith(ME_COMMAND) &&
!this.getBody().startsWith(ME_COMMAND) !this.getBody().startsWith(ME_COMMAND) &&
); !this.bodyIsHeart() &&
!message.bodyIsHeart()
);
} }
private static boolean isStatusMergeable(int a, int b) { private static boolean isStatusMergeable(int a, int b) {
return a == b || ( return a == b || (
( a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND) (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
|| (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND) || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
|| (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND) || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
|| (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED) || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
|| (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND) || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
|| (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED) || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
); );
} }
public String getMergedBody() { public String getMergedBody() {
final Message next = this.next(); final Message next = this.next();
if (this.mergeable(next)) { if (this.mergeable(next)) {
return getBody().trim() + '\n' + next.getMergedBody(); return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody();
} }
return getBody().trim(); return getBody().trim();
} }
@ -435,7 +440,7 @@ public class Message extends AbstractEntity {
* "http://example.com/image.jpg text that will not be shown /abc.png" * "http://example.com/image.jpg text that will not be shown /abc.png"
* or more than one image link in one message. * or more than one image link in one message.
*/ */
if (body.contains(" ")) { if (body.trim().contains(" ")) {
return false; return false;
} }
try { try {
@ -443,7 +448,7 @@ public class Message extends AbstractEntity {
if (!url.getProtocol().equalsIgnoreCase("http") if (!url.getProtocol().equalsIgnoreCase("http")
&& !url.getProtocol().equalsIgnoreCase("https")) { && !url.getProtocol().equalsIgnoreCase("https")) {
return false; return false;
} }
String sUrlPath = url.getPath(); String sUrlPath = url.getPath();
if (sUrlPath == null || sUrlPath.isEmpty()) { if (sUrlPath == null || sUrlPath.isEmpty()) {
@ -457,14 +462,14 @@ public class Message extends AbstractEntity {
String[] extensionParts = sLastUrlPath.split("\\."); String[] extensionParts = sLastUrlPath.split("\\.");
if (extensionParts.length == 2 if (extensionParts.length == 2
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
extensionParts[extensionParts.length - 1])) { extensionParts[extensionParts.length - 1])) {
return true; return true;
} else if (extensionParts.length == 3 } else if (extensionParts.length == 3
&& Arrays && Arrays
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS) .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
.contains(extensionParts[extensionParts.length - 1]) .contains(extensionParts[extensionParts.length - 1])
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
extensionParts[extensionParts.length - 2])) { extensionParts[extensionParts.length - 2])) {
return true; return true;
} else { } else {
return false; return false;
@ -474,6 +479,10 @@ public class Message extends AbstractEntity {
} }
} }
public boolean bodyIsHeart() {
return body != null && UIHelper.HEARTS.contains(body.trim());
}
public ImageParams getImageParams() { public ImageParams getImageParams() {
ImageParams params = getLegacyImageParams(); ImageParams params = getLegacyImageParams();
if (params != null) { if (params != null) {

View file

@ -343,8 +343,6 @@ public class MucOptions {
setError(ERROR_BANNED); setError(ERROR_BANNED);
} else if (error != null && error.hasChild("registration-required")) { } else if (error != null && error.hasChild("registration-required")) {
setError(ERROR_MEMBERS_ONLY); setError(ERROR_MEMBERS_ONLY);
} else {
setError(ERROR_UNKNOWN);
} }
} }
} }

View file

@ -91,7 +91,7 @@ public class IqGenerator extends AbstractGenerator {
return publish("urn:xmpp:avatar:metadata", item); return publish("urn:xmpp:avatar:metadata", item);
} }
public IqPacket retrieveAvatar(final Avatar avatar) { public IqPacket retrievePepAvatar(final Avatar avatar) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum); item.setAttribute("id", avatar.sha1sum);
final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
@ -99,6 +99,13 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket retrieveVcardAvatar(final Avatar avatar) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(avatar.owner);
packet.addChild("vCard","vcard-temp");
return packet;
}
public IqPacket retrieveAvatarMetaData(final Jid to) { public IqPacket retrieveAvatarMetaData(final Jid to) {
final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
if (to != null) { if (to != null) {

View file

@ -49,7 +49,7 @@ public class PresenceGenerator extends AbstractGenerator {
Element cap = packet.addChild("c", Element cap = packet.addChild("c",
"http://jabber.org/protocol/caps"); "http://jabber.org/protocol/caps");
cap.setAttribute("hash", "sha-1"); cap.setAttribute("hash", "sha-1");
cap.setAttribute("node", "http://conversions.im"); cap.setAttribute("node", "http://conversations.im");
cap.setAttribute("ver", capHash); cap.setAttribute("ver", capHash);
} }
return packet; return packet;

View file

@ -494,7 +494,7 @@ public class MessageParser extends AbstractParser implements
} else { } else {
Contact contact = account.getRoster().getContact( Contact contact = account.getRoster().getContact(
from); from);
contact.setAvatar(avatar.getFilename()); contact.setAvatar(avatar);
mXmppConnectionService.getAvatarService().clear( mXmppConnectionService.getAvatarService().clear(
contact); contact);
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();

View file

@ -13,6 +13,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class PresenceParser extends AbstractParser implements public class PresenceParser extends AbstractParser implements
@ -101,6 +102,20 @@ public class PresenceParser extends AbstractParser implements
if (nick != null) { if (nick != null) {
contact.setPresenceName(nick.getContent()); contact.setPresenceName(nick.getContent());
} }
Element x = packet.findChild("x","vcard-temp:x:update");
Avatar avatar = Avatar.parsePresence(x);
if (avatar != null && !contact.isSelf()) {
avatar.owner = from.toBareJid();
if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
if (contact.setAvatar(avatar)) {
mXmppConnectionService.getAvatarService().clear(contact);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateRosterUi();
}
} else {
mXmppConnectionService.fetchAvatar(account,avatar);
}
}
mXmppConnectionService.updateRosterUi(); mXmppConnectionService.updateRosterUi();
} }

View file

@ -4,11 +4,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.Roster;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
import android.content.Context; import android.content.Context;
@ -16,13 +18,14 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DatabaseBackend extends SQLiteOpenHelper { public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null; private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history"; private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 13; private static final int DATABASE_VERSION = 14;
private static String CREATE_CONTATCS_STATEMENT = "create table " private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -130,6 +133,88 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("delete from "+Contact.TABLENAME); db.execSQL("delete from "+Contact.TABLENAME);
db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL");
} }
if (oldVersion < 14 && newVersion >= 14) {
// migrate db to new, canonicalized JID domainpart representation
// Conversation table
Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
while(cursor.moveToNext()) {
String newJid;
try {
newJid = Jid.fromString(
cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
).toString();
} catch (InvalidJidException ignored) {
Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+": " + ignored +". Skipping...");
continue;
}
String updateArgs[] = {
newJid,
cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
};
db.execSQL("update " + Conversation.TABLENAME
+ " set " + Conversation.CONTACTJID + " = ? "
+ " where " + Conversation.UUID + " = ?", updateArgs);
}
cursor.close();
// Contact table
cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
while(cursor.moveToNext()) {
String newJid;
try {
newJid = Jid.fromString(
cursor.getString(cursor.getColumnIndex(Contact.JID))
).toString();
} catch (InvalidJidException ignored) {
Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
+cursor.getString(cursor.getColumnIndex(Contact.JID))
+": " + ignored +". Skipping...");
continue;
}
String updateArgs[] = {
newJid,
cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
cursor.getString(cursor.getColumnIndex(Contact.JID)),
};
db.execSQL("update " + Contact.TABLENAME
+ " set " + Contact.JID + " = ? "
+ " where " + Contact.ACCOUNT + " = ? "
+ " AND " + Contact.JID + " = ?", updateArgs);
}
cursor.close();
// Account table
cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
while(cursor.moveToNext()) {
String newServer;
try {
newServer = Jid.fromParts(
cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
cursor.getString(cursor.getColumnIndex(Account.SERVER)),
"mobile"
).getDomainpart();
} catch (InvalidJidException ignored) {
Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+cursor.getString(cursor.getColumnIndex(Account.SERVER))
+": " + ignored +". Skipping...");
continue;
}
String updateArgs[] = {
newServer,
cursor.getString(cursor.getColumnIndex(Account.UUID)),
};
db.execSQL("update " + Account.TABLENAME
+ " set " + Account.SERVER + " = ? "
+ " where " + Account.UUID + " = ?", updateArgs);
}
cursor.close();
}
} }
public static synchronized DatabaseBackend getInstance(Context context) { public static synchronized DatabaseBackend getInstance(Context context) {

View file

@ -85,7 +85,11 @@ public class NotificationService {
i.putExtra("messageType", "PEBBLE_ALERT"); i.putExtra("messageType", "PEBBLE_ALERT");
i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */ i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */
i.putExtra("notificationData", notificationData); i.putExtra("notificationData", notificationData);
// notify Pebble App
i.setPackage("com.getpebble.android");
mXmppConnectionService.sendBroadcast(i);
// notify Gadgetbridge
i.setPackage("nodomain.freeyourgadget.gadgetbridge");
mXmppConnectionService.sendBroadcast(i); mXmppConnectionService.sendBroadcast(i);
} }

View file

@ -41,6 +41,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -210,6 +211,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
this); this);
private AvatarService mAvatarService = new AvatarService(this); private AvatarService mAvatarService = new AvatarService(this);
private final List<String> mInProgressAvatarFetches = new ArrayList<>();
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
private OnConversationUpdate mOnConversationUpdate = null; private OnConversationUpdate mOnConversationUpdate = null;
private Integer convChangedListenerCount = 0; private Integer convChangedListenerCount = 0;
@ -328,7 +330,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
message.setCounterpart(conversation.getNextCounterpart()); message.setCounterpart(conversation.getNextCounterpart());
} }
if (encryption == Message.ENCRYPTION_DECRYPTED) { if (encryption == Message.ENCRYPTION_DECRYPTED) {
getPgpEngine().encrypt(message,callback); getPgpEngine().encrypt(message, callback);
} else { } else {
callback.success(message); callback.success(message);
} }
@ -347,7 +349,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} }
message.setCounterpart(conversation.getNextCounterpart()); message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE); message.setType(Message.TYPE_FILE);
message.setStatus(Message.STATUS_OFFERED);
String path = getFileBackend().getOriginalPath(uri); String path = getFileBackend().getOriginalPath(uri);
if (path!=null) { if (path!=null) {
message.setRelativeFilePath(path); message.setRelativeFilePath(path);
@ -390,7 +391,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} }
message.setCounterpart(conversation.getNextCounterpart()); message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_IMAGE); message.setType(Message.TYPE_IMAGE);
message.setStatus(Message.STATUS_OFFERED);
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
@ -422,6 +422,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final String action = intent == null ? null : intent.getAction(); final String action = intent == null ? null : intent.getAction();
if (action != null) { if (action != null) {
switch (action) { switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
resetAllAttemptCounts(true);
}
break;
case ACTION_MERGE_PHONE_CONTACTS: case ACTION_MERGE_PHONE_CONTACTS:
if (mRestoredFromDatabase) { if (mRestoredFromDatabase) {
PhoneHelper.loadPhoneContacts(getApplicationContext(), PhoneHelper.loadPhoneContacts(getApplicationContext(),
@ -440,14 +445,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
toggleForegroundService(); toggleForegroundService();
break; break;
case ACTION_TRY_AGAIN: case ACTION_TRY_AGAIN:
for(Account account : accounts) { resetAllAttemptCounts(false);
if (account.hasErrorStatus()) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetAttemptCount();
}
}
}
break; break;
case ACTION_DISABLE_ACCOUNT: case ACTION_DISABLE_ACCOUNT:
try { try {
@ -529,6 +527,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return START_STICKY; return START_STICKY;
} }
private void resetAllAttemptCounts(boolean reallyAll) {
Log.d(Config.LOGTAG,"resetting all attepmt counts");
for(Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetAttemptCount();
}
}
}
}
public boolean hasInternetConnection() { public boolean hasInternetConnection() {
ConnectivityManager cm = (ConnectivityManager) getApplicationContext() ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE); .getSystemService(Context.CONNECTIVITY_SERVICE);
@ -801,7 +811,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Presences presences = contact.getPresences(); Presences presences = contact.getPresences();
if ((message.getCounterpart() != null) if ((message.getCounterpart() != null)
&& (presences.has(message.getCounterpart().getResourcepart()))) { && (presences.has(message.getCounterpart().getResourcepart()))) {
markMessage(message, Message.STATUS_OFFERED);
mJingleConnectionManager.createNewConnection(message); mJingleConnectionManager.createNewConnection(message);
} else { } else {
if (presences.size() == 1) { if (presences.size() == 1) {
@ -811,7 +820,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} catch (InvalidJidException e) { } catch (InvalidJidException e) {
return; return;
} }
markMessage(message, Message.STATUS_OFFERED);
mJingleConnectionManager.createNewConnection(message); mJingleConnectionManager.createNewConnection(message);
} }
} }
@ -1867,6 +1875,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
IqPacket result) { IqPacket result) {
if (result.getType() == IqPacket.TYPE.RESULT) { if (result.getType() == IqPacket.TYPE.RESULT) {
if (account.setAvatar(avatar.getFilename())) { if (account.setAvatar(avatar.getFilename())) {
getAvatarService().clear(account);
databaseBackend.updateAccount(account); databaseBackend.updateAccount(account);
} }
callback.success(avatar); callback.success(avatar);
@ -1893,13 +1902,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
fetchAvatar(account, avatar, null); fetchAvatar(account, avatar, null);
} }
public void fetchAvatar(Account account, final Avatar avatar, private static String generateFetchKey(Account account, final Avatar avatar) {
final UiCallback<Avatar> callback) { return account.getJid().toBareJid()+"_"+avatar.owner+"_"+avatar.sha1sum;
IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar); }
public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
final String KEY = generateFetchKey(account, avatar);
synchronized(this.mInProgressAvatarFetches) {
if (this.mInProgressAvatarFetches.contains(KEY)) {
return;
} else {
switch (avatar.origin) {
case PEP:
this.mInProgressAvatarFetches.add(KEY);
fetchAvatarPep(account, avatar, callback);
break;
case VCARD:
this.mInProgressAvatarFetches.add(KEY);
fetchAvatarVcard(account, avatar, callback);
break;
}
}
}
}
private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar);
sendIqPacket(account, packet, new OnIqPacketReceived() { sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket result) { public void onIqPacketReceived(Account account, IqPacket result) {
synchronized (mInProgressAvatarFetches) {
mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
}
final String ERROR = account.getJid().toBareJid() final String ERROR = account.getJid().toBareJid()
+ ": fetching avatar for " + avatar.owner + " failed "; + ": fetching avatar for " + avatar.owner + " failed ";
if (result.getType() == IqPacket.TYPE.RESULT) { if (result.getType() == IqPacket.TYPE.RESULT) {
@ -1916,7 +1951,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else { } else {
Contact contact = account.getRoster() Contact contact = account.getRoster()
.getContact(avatar.owner); .getContact(avatar.owner);
contact.setAvatar(avatar.getFilename()); contact.setAvatar(avatar);
getAvatarService().clear(contact); getAvatarService().clear(contact);
updateConversationUi(); updateConversationUi();
updateRosterUi(); updateRosterUi();
@ -1925,8 +1960,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.success(avatar); callback.success(avatar);
} }
Log.d(Config.LOGTAG, account.getJid().toBareJid() Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": succesfully fetched avatar for " + ": succesfuly fetched pep avatar for " + avatar.owner);
+ avatar.owner);
return; return;
} }
} else { } else {
@ -1949,8 +1983,38 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}); });
} }
public void checkForAvatar(Account account, private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
final UiCallback<Avatar> callback) { IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
this.sendIqPacket(account,packet,new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
synchronized(mInProgressAvatarFetches) {
mInProgressAvatarFetches.remove(generateFetchKey(account,avatar));
}
if (packet.getType() == IqPacket.TYPE.RESULT) {
Element vCard = packet.findChild("vCard","vcard-temp");
Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
Element binval = photo != null ? photo.findChild("BINVAL") : null;
String image = binval != null ? binval.getContent() : null;
if (image != null) {
avatar.image = image;
if (getFileBackend().save(avatar)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": successfully fetched vCard avatar for " + avatar.owner);
Contact contact = account.getRoster()
.getContact(avatar.owner);
contact.setAvatar(avatar);
getAvatarService().clear(contact);
updateConversationUi();
updateRosterUi();
}
}
}
}
});
}
public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
this.sendIqPacket(account, packet, new OnIqPacketReceived() { this.sendIqPacket(account, packet, new OnIqPacketReceived() {
@ -1972,7 +2036,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
getAvatarService().clear(account); getAvatarService().clear(account);
callback.success(avatar); callback.success(avatar);
} else { } else {
fetchAvatar(account, avatar, callback); fetchAvatarPep(account, avatar, callback);
} }
return; return;
} }
@ -2008,6 +2072,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
disconnect(account, force); disconnect(account, force);
} }
if (!account.isOptionSet(Account.OPTION_DISABLED)) { if (!account.isOptionSet(Account.OPTION_DISABLED)) {
synchronized (this.mInProgressAvatarFetches) {
for(Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext();) {
final String KEY = iterator.next();
if (KEY.startsWith(account.getJid().toBareJid()+"_")) {
iterator.remove();
}
}
}
if (account.getXmppConnection() == null) { if (account.getXmppConnection() == null) {
account.setXmppConnection(createConnection(account)); account.setXmppConnection(createConnection(account));
} }
@ -2031,6 +2105,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} }
public void invite(Conversation conversation, Jid contact) { public void invite(Conversation conversation, Jid contact) {
Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": inviting "+contact+" to "+conversation.getJid().toBareJid());
MessagePacket packet = mMessageGenerator.invite(conversation, contact); MessagePacket packet = mMessageGenerator.invite(conversation, contact);
sendMessagePacket(conversation.getAccount(), packet); sendMessagePacket(conversation.getAccount(), packet);
} }

View file

@ -385,6 +385,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override @Override
void onBackendConnected() { void onBackendConnected() {
if (mPendingConferenceInvite != null) {
mPendingConferenceInvite.execute(this);
mPendingConferenceInvite = null;
}
if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
this.uuid = getIntent().getExtras().getString("uuid"); this.uuid = getIntent().getExtras().getString("uuid");
} }

View file

@ -60,11 +60,11 @@ public class ConversationActivity extends XmppActivity
public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_DECRYPT_PGP = 0x0202;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
private static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PANEL_OPEN = "state_panel_open";
private static final String STATE_PENDING_URI = "state_pending_uri"; private static final String STATE_PENDING_URI = "state_pending_uri";
@ -398,61 +398,88 @@ public class ConversationActivity extends XmppActivity
} }
private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
final OnPresenceSelected callback = new OnPresenceSelected() {
@Override
public void onPresenceSelected() {
Intent intent = new Intent();
boolean chooser = false;
String fallbackPackageId = null;
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
}
intent.setType("image/*");
chooser = true;
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
mPendingImageUris.add(uri);
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
chooser = true;
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
fallbackPackageId = "eu.siacs.conversations.voicerecorder";
break;
case ATTACHMENT_CHOICE_LOCATION:
intent.setAction("eu.siacs.conversations.location.request");
fallbackPackageId = "eu.siacs.conversations.sharelocation";
break;
}
if (intent.resolveActivity(getPackageManager()) != null) {
if (chooser) {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.perform_action_with)),
attachmentChoice);
} else {
startActivityForResult(intent, attachmentChoice);
}
} else if (fallbackPackageId != null) {
startActivity(getInstallApkIntent(fallbackPackageId));
}
}
};
if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) { if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) {
getSelectedConversation().setNextCounterpart(null); getSelectedConversation().setNextCounterpart(null);
Intent intent = new Intent("eu.siacs.conversations.location.request"); callback.onPresenceSelected();
startActivityForResult(intent,attachmentChoice);
} else { } else {
selectPresence(getSelectedConversation(), new OnPresenceSelected() { selectPresence(getSelectedConversation(),callback);
@Override
public void onPresenceSelected() {
Intent intent = new Intent();
boolean chooser = false;
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
}
intent.setType("image/*");
chooser = true;
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
mPendingImageUris.add(uri);
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
chooser = true;
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
break;
case ATTACHMENT_CHOICE_LOCATION:
intent.setAction("eu.siacs.conversations.location.request");
break;
}
if (intent.resolveActivity(getPackageManager()) != null) {
if (chooser) {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.perform_action_with)),
attachmentChoice);
} else {
startActivityForResult(intent, attachmentChoice);
}
}
}
});
} }
} }
private void attachFile(final int attachmentChoice) { private Intent getInstallApkIntent(final String packageId) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://details?id="+packageId));
if (intent.resolveActivity(getPackageManager()) != null) {
return intent;
} else {
intent.setData(Uri.parse("http://play.google.com/store/apps/details?id="+packageId));
return intent;
}
}
public void attachFile(final int attachmentChoice) {
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_LOCATION:
getPreferences().edit().putString("recently_used_quick_action","location").apply();
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
getPreferences().edit().putString("recently_used_quick_action","voice").apply();
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
getPreferences().edit().putString("recently_used_quick_action","photo").apply();
break;
}
final Conversation conversation = getSelectedConversation(); final Conversation conversation = getSelectedConversation();
final int encryption = conversation.getNextEncryption(forceEncryption()); final int encryption = conversation.getNextEncryption(forceEncryption());
if (encryption == Message.ENCRYPTION_PGP) { if (encryption == Message.ENCRYPTION_PGP) {
@ -875,6 +902,12 @@ public class ConversationActivity extends XmppActivity
void onBackendConnected() { void onBackendConnected() {
this.xmppConnectionService.getNotificationService().setIsInForeground(true); this.xmppConnectionService.getNotificationService().setIsInForeground(true);
updateConversationList(); updateConversationList();
if (mPendingConferenceInvite != null) {
mPendingConferenceInvite.execute(this);
mPendingConferenceInvite = null;
}
if (xmppConnectionService.getAccounts().size() == 0) { if (xmppConnectionService.getAccounts().size() == 0) {
if (!mRedirected) { if (!mRedirected) {
this.mRedirected = true; this.mRedirected = true;
@ -901,9 +934,7 @@ public class ConversationActivity extends XmppActivity
} }
this.mConversationFragment.reInit(getSelectedConversation()); this.mConversationFragment.reInit(getSelectedConversation());
mOpenConverstaion = null; mOpenConverstaion = null;
} else if (getSelectedConversation() != null) { } else if (getSelectedConversation() == null) {
this.mConversationFragment.reInit(getSelectedConversation());
} else {
showConversationsOverview(); showConversationsOverview();
mPendingImageUris.clear(); mPendingImageUris.clear();
mPendingFileUris.clear(); mPendingFileUris.clear();

View file

@ -59,6 +59,7 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
@ -117,16 +118,37 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
private int getIndexOf(String uuid, List<Message> messages) {
if (uuid == null) {
return 0;
}
for(int i = 0; i < messages.size(); ++i) {
if (uuid.equals(messages.get(i).getUuid())) {
return i;
} else {
Message next = messages.get(i);
while(next != null && next.wasMergedIntoPrevious()) {
if (uuid.equals(next.getUuid())) {
return i;
}
next = next.next();
}
}
}
return 0;
}
@Override @Override
public void onScroll(AbsListView view, int firstVisibleItem, public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) { int visibleItemCount, int totalItemCount) {
synchronized (ConversationFragment.this.messageList) { synchronized (ConversationFragment.this.messageList) {
if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) {
long timestamp = ConversationFragment.this.messageList.get(0).getTimeSent(); long timestamp = ConversationFragment.this.messageList.get(0).getTimeSent();
messagesLoaded = false; messagesLoaded = false;
activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
@Override @Override
public void onMoreMessagesLoaded(final int count, Conversation conversation) { public void onMoreMessagesLoaded(final int c, Conversation conversation) {
if (ConversationFragment.this.conversation != conversation) { if (ConversationFragment.this.conversation != conversation) {
return; return;
} }
@ -134,29 +156,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void run() { public void run() {
final int oldPosition = messagesView.getFirstVisiblePosition(); final int oldPosition = messagesView.getFirstVisiblePosition();
Message message = messageList.get(oldPosition);
String uuid = message != null ? message.getUuid() : null;
View v = messagesView.getChildAt(0); View v = messagesView.getChildAt(0);
final int pxOffset = (v == null) ? 0 : v.getTop(); final int pxOffset = (v == null) ? 0 : v.getTop();
ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
updateStatusMessages(); updateStatusMessages();
messageListAdapter.notifyDataSetChanged(); messageListAdapter.notifyDataSetChanged();
if (count != 0) { int pos = getIndexOf(uuid,messageList);
final int newPosition = oldPosition + count; messagesView.setSelectionFromTop(pos, pxOffset);
int offset = 0; messagesLoaded = true;
try { if (messageLoaderToast != null) {
Message tmpMessage = messageList.get(newPosition); messageLoaderToast.cancel();
while(tmpMessage.wasMergedIntoPrevious()) {
offset++;
tmpMessage = tmpMessage.prev();
}
} catch (final IndexOutOfBoundsException ignored) {
}
messagesView.setSelectionFromTop(newPosition - offset, pxOffset);
messagesLoaded = true;
if (messageLoaderToast != null) {
messageLoaderToast.cancel();
}
} }
} }
}); });
@ -174,7 +185,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (ConversationFragment.this.conversation != conversation) { if (ConversationFragment.this.conversation != conversation) {
return; return;
} }
messageLoaderToast = Toast.makeText(activity,resId,Toast.LENGTH_LONG); messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
messageLoaderToast.show(); messageLoaderToast.show();
} }
}); });
@ -208,7 +219,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onClick(View v) { public void onClick(View v) {
activity.verifyOtrSessionDialog(conversation,v); activity.verifyOtrSessionDialog(conversation, v);
} }
}; };
private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>();
@ -219,7 +230,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) { if (actionId == EditorInfo.IME_ACTION_SEND) {
InputMethodManager imm = (InputMethodManager) v.getContext() InputMethodManager imm = (InputMethodManager) v.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE); .getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0); imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
sendMessage(); sendMessage();
return true; return true;
@ -232,15 +243,39 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onClick(View v) { public void onClick(View v) {
sendMessage(); Object tag = v.getTag();
if (tag instanceof SendButtonAction) {
SendButtonAction action = (SendButtonAction) tag;
switch (action) {
case TAKE_PHOTO:
activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO);
break;
case SEND_LOCATION:
activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION);
break;
case RECORD_VOICE:
activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE);
break;
case CANCEL:
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setNextCounterpart(null);
updateChatMsgHint();
updateSendButton();
}
break;
default:
sendMessage();
}
} else {
sendMessage();
}
} }
}; };
private OnClickListener clickToMuc = new OnClickListener() { private OnClickListener clickToMuc = new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(getActivity(), Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class);
ConferenceDetailsActivity.class);
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
intent.putExtra("uuid", conversation.getUuid()); intent.putExtra("uuid", conversation.getUuid());
startActivity(intent); startActivity(intent);
@ -253,16 +288,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (this.conversation == null) { if (this.conversation == null) {
return; return;
} }
if (mEditMessage.getText().length() < 1) {
if (this.conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setNextCounterpart(null);
updateChatMsgHint();
}
return;
}
Message message = new Message(conversation, mEditMessage.getText() Message message = new Message(conversation, mEditMessage.getText()
.toString(), conversation.getNextEncryption(activity .toString(), conversation.getNextEncryption(activity
.forceEncryption())); .forceEncryption()));
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getNextCounterpart() != null) { if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart()); message.setCounterpart(conversation.getNextCounterpart());
@ -282,13 +310,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (conversation.getMode() == Conversation.MODE_MULTI if (conversation.getMode() == Conversation.MODE_MULTI
&& conversation.getNextCounterpart() != null) { && conversation.getNextCounterpart() != null) {
this.mEditMessage.setHint(getString( this.mEditMessage.setHint(getString(
R.string.send_private_message_to, R.string.send_private_message_to,
conversation.getNextCounterpart().getResourcepart())); conversation.getNextCounterpart().getResourcepart()));
} else { } else {
switch (conversation.getNextEncryption(activity.forceEncryption())) { switch (conversation.getNextEncryption(activity.forceEncryption())) {
case Message.ENCRYPTION_NONE: case Message.ENCRYPTION_NONE:
mEditMessage mEditMessage
.setHint(getString(R.string.send_plain_text_message)); .setHint(getString(R.string.send_plain_text_message));
break; break;
case Message.ENCRYPTION_OTR: case Message.ENCRYPTION_OTR:
mEditMessage.setHint(getString(R.string.send_otr_message)); mEditMessage.setHint(getString(R.string.send_otr_message));
@ -304,7 +332,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
private void setupIme() { private void setupIme() {
if (((ConversationActivity)getActivity()).usingEnterKey()) { if (((ConversationActivity) getActivity()).usingEnterKey()) {
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
} else { } else {
mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
@ -313,8 +341,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public View onCreateView(final LayoutInflater inflater, public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_conversation,container, false); final View view = inflater.inflate(R.layout.fragment_conversation, container, false);
view.setOnClickListener(null); view.setOnClickListener(null);
mEditMessage = (EditMessage) view.findViewById(R.id.textinput); mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
setupIme(); setupIme();
@ -365,21 +393,21 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
}); });
messageListAdapter messageListAdapter
.setOnContactPictureLongClicked(new OnContactPictureLongClicked() { .setOnContactPictureLongClicked(new OnContactPictureLongClicked() {
@Override @Override
public void onContactPictureLongClicked(Message message) { public void onContactPictureLongClicked(Message message) {
if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getStatus() <= Message.STATUS_RECEIVED) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
if (message.getCounterpart() != null) { if (message.getCounterpart() != null) {
privateMessageWith(message.getCounterpart()); privateMessageWith(message.getCounterpart());
}
} }
} else {
activity.showQrCode();
} }
} else {
activity.showQrCode();
} }
} });
});
messagesView.setAdapter(messageListAdapter); messagesView.setAdapter(messageListAdapter);
registerForContextMenu(messagesView); registerForContextMenu(messagesView);
@ -389,7 +417,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) { ContextMenuInfo menuInfo) {
synchronized (this.messageList) { synchronized (this.messageList) {
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
@ -416,7 +444,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if ((m.getType() == Message.TYPE_TEXT if ((m.getType() == Message.TYPE_TEXT
|| m.getType() == Message.TYPE_PRIVATE || m.getType() == Message.TYPE_PRIVATE
|| m.getDownloadable() != null) || m.getDownloadable() != null)
&& (!GeoHelper.isGeoUri(m.getBody()))) { && (!GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(false); shareWith.setVisible(false);
} }
if (m.getStatus() != Message.STATUS_SEND_FAILED) { if (m.getStatus() != Message.STATUS_SEND_FAILED) {
@ -425,17 +453,17 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) if (((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null)
|| m.getImageParams().url == null) && !GeoHelper.isGeoUri(m.getBody())) { || m.getImageParams().url == null) && !GeoHelper.isGeoUri(m.getBody())) {
copyUrl.setVisible(false); copyUrl.setVisible(false);
} }
if (m.getType() != Message.TYPE_TEXT if (m.getType() != Message.TYPE_TEXT
|| m.getDownloadable() != null || m.getDownloadable() != null
|| !m.bodyContainsDownloadable()) { || !m.bodyContainsDownloadable()) {
downloadImage.setVisible(false); downloadImage.setVisible(false);
} }
if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder))
|| (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING
|| m.getStatus() == Message.STATUS_OFFERED)))) { || m.getStatus() == Message.STATUS_OFFERED)))) {
cancelTransmission.setVisible(false); cancelTransmission.setVisible(false);
} }
} }
} }
@ -483,12 +511,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
shareIntent.setType(mime); shareIntent.setType(mime);
} }
activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with))); activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
} }
private void copyText(Message message) { private void copyText(Message message) {
if (activity.copyTextToClipboard(message.getMergedBody(), if (activity.copyTextToClipboard(message.getMergedBody(),
R.string.message_text)) { R.string.message_text)) {
Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.makeText(activity, R.string.message_copied_to_clipboard,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
@ -498,7 +526,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
if (!file.exists()) { if (!file.exists()) {
Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
return; return;
} }
@ -519,20 +547,20 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (activity.copyTextToClipboard(url, resId)) { if (activity.copyTextToClipboard(url, resId)) {
Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.makeText(activity, R.string.url_copied_to_clipboard,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
} }
private void downloadImage(Message message) { private void downloadImage(Message message) {
activity.xmppConnectionService.getHttpConnectionManager() activity.xmppConnectionService.getHttpConnectionManager()
.createNewConnection(message); .createNewConnection(message);
} }
private void cancelTransmission(Message message) { private void cancelTransmission(Message message) {
Downloadable downloadable = message.getDownloadable(); Downloadable downloadable = message.getDownloadable();
if (downloadable!=null) { if (downloadable != null) {
downloadable.cancel(); downloadable.cancel();
} else { } else {
activity.xmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
} }
} }
@ -540,6 +568,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mEditMessage.setText(""); this.mEditMessage.setText("");
this.conversation.setNextCounterpart(counterpart); this.conversation.setNextCounterpart(counterpart);
updateChatMsgHint(); updateChatMsgHint();
updateSendButton();
} }
protected void highlightInConference(String nick) { protected void highlightInConference(String nick) {
@ -548,9 +577,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
mEditMessage.getText().insert(0, nick + ": "); mEditMessage.getText().insert(0, nick + ": ");
} else { } else {
if (mEditMessage.getText().charAt( if (mEditMessage.getText().charAt(
mEditMessage.getSelectionStart() - 1) != ' ') { mEditMessage.getSelectionStart() - 1) != ' ') {
nick = " " + nick; nick = " " + nick;
} }
mEditMessage.getText().insert(mEditMessage.getSelectionStart(), mEditMessage.getText().insert(mEditMessage.getSelectionStart(),
nick + " "); nick + " ");
} }
@ -563,7 +592,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (this.conversation != null) { if (this.conversation != null) {
final String msg = mEditMessage.getText().toString(); final String msg = mEditMessage.getText().toString();
this.conversation.setNextMessage(msg); this.conversation.setNextMessage(msg);
updateChatState(this.conversation,msg); updateChatState(this.conversation, msg);
} }
} }
@ -586,7 +615,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final String msg = mEditMessage.getText().toString(); final String msg = mEditMessage.getText().toString();
this.conversation.setNextMessage(msg); this.conversation.setNextMessage(msg);
if (this.conversation != conversation) { if (this.conversation != conversation) {
updateChatState(this.conversation,msg); updateChatState(this.conversation, msg);
} }
this.conversation.trim(); this.conversation.trim();
} }
@ -632,7 +661,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onClick(View v) { public void onClick(View v) {
final Contact contact = conversation == null ? null :conversation.getContact(); final Contact contact = conversation == null ? null : conversation.getContact();
if (contact != null) { if (contact != null) {
activity.xmppConnectionService.createContact(contact); activity.xmppConnectionService.createContact(contact);
activity.switchToContactDetails(contact); activity.switchToContactDetails(contact);
@ -655,7 +684,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString());
intent.putExtra("mode",VerifyOTRActivity.MODE_ANSWER_QUESTION); intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION);
startActivity(intent); startActivity(intent);
} }
}; };
@ -665,11 +694,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final Contact contact = conversation.getContact(); final Contact contact = conversation.getContact();
final int mode = conversation.getMode(); final int mode = conversation.getMode();
if (conversation.isBlocked()) { if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock,this.mUnblockClickListener); showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
} else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
showSnackbar(R.string.contact_added_you, R.string.add_back,this.mAddBackClickListener); showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener);
} else if (mode == Conversation.MODE_MULTI } else if (mode == Conversation.MODE_MULTI
&&!conversation.getMucOptions().online() && !conversation.getMucOptions().online()
&& account.getStatus() == Account.State.ONLINE) { && account.getStatus() == Account.State.ONLINE) {
switch (conversation.getMucOptions().getError()) { switch (conversation.getMucOptions().getError()) {
case MucOptions.ERROR_NICK_IN_USE: case MucOptions.ERROR_NICK_IN_USE:
@ -693,18 +722,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
default: default:
break; break;
} }
} else if (askForPassphraseIntent != null ) { } else if (askForPassphraseIntent != null) {
showSnackbar(R.string.openpgp_messages_found,R.string.decrypt, clickToDecryptListener); showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
} else if (mode == Conversation.MODE_SINGLE } else if (mode == Conversation.MODE_SINGLE
&& conversation.smpRequested()) { && conversation.smpRequested()) {
showSnackbar(R.string.smp_requested, R.string.verify,this.mAnswerSmpClickListener); showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener);
} else if (mode == Conversation.MODE_SINGLE } else if (mode == Conversation.MODE_SINGLE
&&conversation.hasValidOtrSession() && conversation.hasValidOtrSession()
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
&& (!conversation.isOtrFingerprintVerified())) { && (!conversation.isOtrFingerprintVerified())) {
showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
} else if (conversation.isMuted()) { } else if (conversation.isMuted()) {
showSnackbar(R.string.notifications_disabled, R.string.enable,this.mUnmuteClickListener); showSnackbar(R.string.notifications_disabled, R.string.enable, this.mUnmuteClickListener);
} else { } else {
hideSnackbar(); hideSnackbar();
} }
@ -722,12 +751,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
for (final Message message : this.messageList) { for (final Message message : this.messageList) {
if (message.getEncryption() == Message.ENCRYPTION_PGP if (message.getEncryption() == Message.ENCRYPTION_PGP
&& (message.getStatus() == Message.STATUS_RECEIVED || message && (message.getStatus() == Message.STATUS_RECEIVED || message
.getStatus() >= Message.STATUS_SEND) .getStatus() >= Message.STATUS_SEND)
&& message.getDownloadable() == null) { && message.getDownloadable() == null) {
if (!mEncryptedMessages.contains(message)) { if (!mEncryptedMessages.contains(message)) {
mEncryptedMessages.add(message); mEncryptedMessages.add(message);
} }
} }
} }
decryptNext(); decryptNext();
updateStatusMessages(); updateStatusMessages();
@ -790,53 +819,128 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateChatMsgHint(); updateChatMsgHint();
} }
enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL}
private int getSendButtonImageResource(SendButtonAction action, int status) {
switch (action) {
case TEXT:
switch (status) {
case Presences.CHAT:
case Presences.ONLINE:
return R.drawable.ic_send_text_online;
case Presences.AWAY:
return R.drawable.ic_send_text_away;
case Presences.XA:
case Presences.DND:
return R.drawable.ic_send_text_dnd;
default:
return R.drawable.ic_send_text_offline;
}
case TAKE_PHOTO:
switch (status) {
case Presences.CHAT:
case Presences.ONLINE:
return R.drawable.ic_send_photo_online;
case Presences.AWAY:
return R.drawable.ic_send_photo_away;
case Presences.XA:
case Presences.DND:
return R.drawable.ic_send_photo_dnd;
default:
return R.drawable.ic_send_photo_offline;
}
case RECORD_VOICE:
switch (status) {
case Presences.CHAT:
case Presences.ONLINE:
return R.drawable.ic_send_voice_online;
case Presences.AWAY:
return R.drawable.ic_send_voice_away;
case Presences.XA:
case Presences.DND:
return R.drawable.ic_send_voice_dnd;
default:
return R.drawable.ic_send_voice_offline;
}
case SEND_LOCATION:
switch (status) {
case Presences.CHAT:
case Presences.ONLINE:
return R.drawable.ic_send_location_online;
case Presences.AWAY:
return R.drawable.ic_send_location_away;
case Presences.XA:
case Presences.DND:
return R.drawable.ic_send_location_dnd;
default:
return R.drawable.ic_send_location_offline;
}
case CANCEL:
switch (status) {
case Presences.CHAT:
case Presences.ONLINE:
return R.drawable.ic_send_cancel_online;
case Presences.AWAY:
return R.drawable.ic_send_cancel_away;
case Presences.XA:
case Presences.DND:
return R.drawable.ic_send_cancel_dnd;
default:
return R.drawable.ic_send_cancel_offline;
}
}
return R.drawable.ic_send_text_offline;
}
public void updateSendButton() { public void updateSendButton() {
Conversation c = this.conversation; final Conversation c = this.conversation;
final SendButtonAction action;
final int status;
final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0;
if (c.getMode() == Conversation.MODE_MULTI) {
if (empty && c.getNextCounterpart() != null) {
action = SendButtonAction.CANCEL;
} else {
action = SendButtonAction.TEXT;
}
} else {
if (empty) {
String setting = activity.getPreferences().getString("quick_action","recent");
if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) {
setting = "location";
} else if (setting.equals("recent")) {
setting = activity.getPreferences().getString("recently_used_quick_action","text");
}
switch (setting) {
case "photo":
action = SendButtonAction.TAKE_PHOTO;
break;
case "location":
action = SendButtonAction.SEND_LOCATION;
break;
case "voice":
action = SendButtonAction.RECORD_VOICE;
break;
default:
action = SendButtonAction.TEXT;
break;
}
} else {
action = SendButtonAction.TEXT;
}
}
if (activity.useSendButtonToIndicateStatus() && c != null if (activity.useSendButtonToIndicateStatus() && c != null
&& c.getAccount().getStatus() == Account.State.ONLINE) { && c.getAccount().getStatus() == Account.State.ONLINE) {
if (c.getMode() == Conversation.MODE_SINGLE) { if (c.getMode() == Conversation.MODE_SINGLE) {
switch (c.getContact().getMostAvailableStatus()) { status = c.getContact().getMostAvailableStatus();
case Presences.CHAT:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_online);
break;
case Presences.ONLINE:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_online);
break;
case Presences.AWAY:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_away);
break;
case Presences.XA:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_away);
break;
case Presences.DND:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_dnd);
break;
default:
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_offline);
break;
}
} else if (c.getMode() == Conversation.MODE_MULTI) {
if (c.getMucOptions().online()) {
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_online);
} else {
this.mSendButton
.setImageResource(R.drawable.ic_action_send_now_offline);
}
} else { } else {
this.mSendButton status = c.getMucOptions().online() ? Presences.ONLINE : Presences.OFFLINE;
.setImageResource(R.drawable.ic_action_send_now_offline);
} }
} else { } else {
this.mSendButton status = Presences.OFFLINE;
.setImageResource(R.drawable.ic_action_send_now_offline);
} }
this.mSendButton.setTag(action);
this.mSendButton.setImageResource(getSendButtonImageResource(action, status));
} }
protected void updateStatusMessages() { protected void updateStatusMessages() {
@ -865,7 +969,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
protected void showSnackbar(final int message, final int action, protected void showSnackbar(final int message, final int action,
final OnClickListener clickListener) { final OnClickListener clickListener) {
snackbar.setVisibility(View.VISIBLE); snackbar.setVisibility(View.VISIBLE);
snackbar.setOnClickListener(null); snackbar.setOnClickListener(null);
snackbarMessage.setText(message); snackbarMessage.setText(message);
@ -897,7 +1001,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void userInputRequried(PendingIntent pi, public void userInputRequried(PendingIntent pi,
Contact contact) { Contact contact) {
activity.runIntent( activity.runIntent(
pi, pi,
ConversationActivity.REQUEST_ENCRYPT_MESSAGE); ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
@ -921,11 +1025,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_NONE); .setNextEncryption(Message.ENCRYPTION_NONE);
xmppService.databaseBackend xmppService.databaseBackend
.updateConversation(conversation); .updateConversation(conversation);
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage(message); xmppService.sendMessage(message);
messageSent(); messageSent();
@ -936,9 +1040,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (conversation.getMucOptions().pgpKeysInUse()) { if (conversation.getMucOptions().pgpKeysInUse()) {
if (!conversation.getMucOptions().everybodyHasKeys()) { if (!conversation.getMucOptions().everybodyHasKeys()) {
Toast warning = Toast Toast warning = Toast
.makeText(getActivity(), .makeText(getActivity(),
R.string.missing_public_keys, R.string.missing_public_keys,
Toast.LENGTH_LONG); Toast.LENGTH_LONG);
warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
warning.show(); warning.show();
} }
@ -950,12 +1054,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override @Override
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_NONE); .setNextEncryption(Message.ENCRYPTION_NONE);
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.databaseBackend xmppService.databaseBackend
.updateConversation(conversation); .updateConversation(conversation);
xmppService.sendMessage(message); xmppService.sendMessage(message);
messageSent(); messageSent();
} }
@ -968,7 +1072,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} }
public void showNoPGPKeyDialog(boolean plural, public void showNoPGPKeyDialog(boolean plural,
DialogInterface.OnClickListener listener) { DialogInterface.OnClickListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setIconAttribute(android.R.attr.alertDialogIcon);
if (plural) { if (plural) {
@ -1026,6 +1130,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
activity.xmppConnectionService.sendChatState(conversation); activity.xmppConnectionService.sendChatState(conversation);
} }
updateSendButton();
} }
@Override @Override
@ -1042,6 +1147,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
activity.xmppConnectionService.sendChatState(conversation); activity.xmppConnectionService.sendChatState(conversation);
} }
updateSendButton();
} }
} }

View file

@ -223,7 +223,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (avatar != null) { if (avatar != null) {
intent = new Intent(getApplicationContext(), intent = new Intent(getApplicationContext(),
StartConversationActivity.class); StartConversationActivity.class);
intent.putExtra("init",true); if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) {
intent.putExtra("init", true);
}
} else { } else {
intent = new Intent(getApplicationContext(), intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class); PublishProfilePictureActivity.class);

View file

@ -116,7 +116,9 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (mInitialAccountSetup) { if (mInitialAccountSetup) {
Intent intent = new Intent(getApplicationContext(), Intent intent = new Intent(getApplicationContext(),
StartConversationActivity.class); StartConversationActivity.class);
intent.putExtra("init",true); if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) {
intent.putExtra("init", true);
}
startActivity(intent); startActivity(intent);
} }
finish(); finish();

View file

@ -65,6 +65,7 @@ import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.ui.adapter.ListItemAdapter; import eu.siacs.conversations.ui.adapter.ListItemAdapter;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
@ -757,14 +758,16 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
} else { } else {
activity.contact_context_id = acmi.position; activity.contact_context_id = acmi.position;
final Blockable contact = (Contact) activity.contacts.get(acmi.position); final Blockable contact = (Contact) activity.contacts.get(acmi.position);
final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
if (blockUnblockItem != null) { XmppConnection xmpp = contact.getAccount().getXmppConnection();
if (xmpp != null && xmpp.getFeatures().blocking()) {
if (contact.isBlocked()) { if (contact.isBlocked()) {
blockUnblockItem.setTitle(R.string.unblock_contact); blockUnblockItem.setTitle(R.string.unblock_contact);
} else { } else {
blockUnblockItem.setTitle(R.string.block_contact); blockUnblockItem.setTitle(R.string.block_contact);
} }
} else {
blockUnblockItem.setVisible(false);
} }
} }
} }

View file

@ -113,6 +113,8 @@ public abstract class XmppActivity extends Activity {
} }
}; };
protected ConferenceInvite mPendingConferenceInvite = null;
protected void refreshUi() { protected void refreshUi() {
final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
@ -367,7 +369,7 @@ public abstract class XmppActivity extends Activity {
} }
public void highlightInMuc(Conversation conversation, String nick) { public void highlightInMuc(Conversation conversation, String nick) {
switchToConversation(conversation,null,nick,false); switchToConversation(conversation, null, nick, false);
} }
private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) { private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) {
@ -435,7 +437,7 @@ public abstract class XmppActivity extends Activity {
@Override @Override
public void userInputRequried(PendingIntent pi, public void userInputRequried(PendingIntent pi,
Account account) { Account account) {
try { try {
startIntentSenderForResult(pi.getIntentSender(), startIntentSenderForResult(pi.getIntentSender(),
REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
@ -446,13 +448,13 @@ public abstract class XmppActivity extends Activity {
@Override @Override
public void success(Account account) { public void success(Account account) {
xmppConnectionService.databaseBackend xmppConnectionService.databaseBackend
.updateAccount(account); .updateAccount(account);
xmppConnectionService.sendPresence(account); xmppConnectionService.sendPresence(account);
if (conversation != null) { if (conversation != null) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_PGP); .setNextEncryption(Message.ENCRYPTION_PGP);
xmppConnectionService.databaseBackend xmppConnectionService.databaseBackend
.updateConversation(conversation); .updateConversation(conversation);
} }
} }
@ -665,32 +667,11 @@ public abstract class XmppActivity extends Activity {
protected void onActivityResult(int requestCode, int resultCode, protected void onActivityResult(int requestCode, int resultCode,
final Intent data) { final Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_INVITE_TO_CONVERSATION if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
&& resultCode == RESULT_OK) { mPendingConferenceInvite = ConferenceInvite.parse(data);
try { if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
String conversationUuid = data.getStringExtra("conversation"); mPendingConferenceInvite.execute(this);
Conversation conversation = xmppConnectionService mPendingConferenceInvite = null;
.findConversationByUuid(conversationUuid);
List<Jid> jids = new ArrayList<Jid>();
if (data.getBooleanExtra("multiple", false)) {
String[] toAdd = data.getStringArrayExtra("contacts");
for (String item : toAdd) {
jids.add(Jid.fromString(item));
}
} else {
jids.add(Jid.fromString(data.getStringExtra("contact")));
}
if (conversation.getMode() == Conversation.MODE_MULTI) {
for (Jid jid : jids) {
xmppConnectionService.invite(conversation, jid);
}
} else {
jids.add(conversation.getJid().toBareJid());
xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback);
}
} catch (final InvalidJidException ignored) {
} }
} }
} }
@ -855,6 +836,48 @@ public abstract class XmppActivity extends Activity {
} }
} }
public static class ConferenceInvite {
private String uuid;
private List<Jid> jids = new ArrayList<>();
public static ConferenceInvite parse(Intent data) {
ConferenceInvite invite = new ConferenceInvite();
invite.uuid = data.getStringExtra("conversation");
if (invite.uuid == null) {
return null;
}
try {
if (data.getBooleanExtra("multiple", false)) {
String[] toAdd = data.getStringArrayExtra("contacts");
for (String item : toAdd) {
invite.jids.add(Jid.fromString(item));
}
} else {
invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
}
} catch (final InvalidJidException ignored) {
return null;
}
return invite;
}
public void execute(XmppActivity activity) {
XmppConnectionService service = activity.xmppConnectionService;
Conversation conversation = service.findConversationByUuid(this.uuid);
if (conversation == null) {
return;
}
if (conversation.getMode() == Conversation.MODE_MULTI) {
for (Jid jid : jids) {
service.invite(conversation, jid);
}
} else {
jids.add(conversation.getJid().toBareJid());
service.createAdhocConference(conversation.getAccount(), jids, activity.adhocCallback);
}
}
}
public AvatarService avatarService() { public AvatarService avatarService() {
return xmppConnectionService.getAvatarService(); return xmppConnectionService.getAvatarService();
} }

View file

@ -7,10 +7,11 @@ import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener; import android.view.View.OnLongClickListener;
@ -24,7 +25,6 @@ import android.widget.Toast;
import java.util.List; import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
@ -42,7 +42,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
private static final int SENT = 0; private static final int SENT = 0;
private static final int RECEIVED = 1; private static final int RECEIVED = 1;
private static final int STATUS = 2; private static final int STATUS = 2;
private static final int NULL = 3;
private ConversationActivity activity; private ConversationActivity activity;
@ -77,14 +76,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
@Override @Override
public int getViewTypeCount() { public int getViewTypeCount() {
return 4; return 3;
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (getItem(position).wasMergedIntoPrevious()) { if (getItem(position).getType() == Message.TYPE_STATUS) {
return NULL;
} else if (getItem(position).getType() == Message.TYPE_STATUS) {
return STATUS; return STATUS;
} else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
return RECEIVED; return RECEIVED;
@ -207,22 +204,42 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.image.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getContext().getString( viewHolder.messageBody.setText(getContext().getString(
R.string.decryption_failed)); R.string.decryption_failed));
viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setTextIsSelectable(false); viewHolder.messageBody.setTextIsSelectable(false);
} }
private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setIncludeFontPadding(false);
Spannable span = new SpannableString(body);
span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
viewHolder.messageBody.setText(span);
}
private void displayTextMessage(final ViewHolder viewHolder, final Message message) { private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
if (viewHolder.download_button != null) { if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.GONE);
} }
viewHolder.image.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setIncludeFontPadding(true);
if (message.getBody() != null) { if (message.getBody() != null) {
final String nick = UIHelper.getMessageDisplayName(message); final String nick = UIHelper.getMessageDisplayName(message);
final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " ");
nick + " "); final SpannableString formattedBody = new SpannableString(body);
int i = body.indexOf(Message.MERGE_SEPARATOR);
while(i >= 0) {
final int end = i + Message.MERGE_SEPARATOR.length();
formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
i = body.indexOf(Message.MERGE_SEPARATOR,end);
}
if (message.getType() != Message.TYPE_PRIVATE) { if (message.getType() != Message.TYPE_PRIVATE) {
if (message.hasMeCommand()) { if (message.hasMeCommand()) {
final Spannable span = new SpannableString(formattedBody); final Spannable span = new SpannableString(formattedBody);
@ -230,7 +247,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
viewHolder.messageBody.setText(span); viewHolder.messageBody.setText(span);
} else { } else {
viewHolder.messageBody.setText(message.getMergedBody()); viewHolder.messageBody.setText(formattedBody);
} }
} else { } else {
String privateMarker; String privateMarker;
@ -289,7 +306,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.image.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity,message))); viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
viewHolder.download_button.setOnClickListener(new OnClickListener() { viewHolder.download_button.setOnClickListener(new OnClickListener() {
@Override @Override
@ -334,7 +351,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
scalledH = (int) (params.height / ((double) params.width / target)); scalledH = (int) (params.height / ((double) params.width / target));
} }
viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
scalledW, scalledH)); scalledW, scalledH));
activity.loadBitmap(message, viewHolder.image); activity.loadBitmap(message, viewHolder.image);
viewHolder.image.setOnClickListener(new OnClickListener() { viewHolder.image.setOnClickListener(new OnClickListener() {
@ -359,10 +376,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (view == null) { if (view == null) {
viewHolder = new ViewHolder(); viewHolder = new ViewHolder();
switch (type) { switch (type) {
case NULL:
view = activity.getLayoutInflater().inflate(
R.layout.message_null, parent, false);
break;
case SENT: case SENT:
view = activity.getLayoutInflater().inflate( view = activity.getLayoutInflater().inflate(
R.layout.message_sent, parent, false); R.layout.message_sent, parent, false);
@ -429,25 +442,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.status_message.setText(message.getBody()); viewHolder.status_message.setText(message.getBody());
} }
return view; return view;
} else if (type == NULL) {
if (viewHolder.message_box != null) {
Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view");
view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false);
view.setTag(new ViewHolder());
}
if (position == getCount() - 1) {
view.getLayoutParams().height = 1;
} else {
view.getLayoutParams().height = 0;
}
view.setLayoutParams(view.getLayoutParams());
return view;
} else if (message.wasMergedIntoPrevious()) {
Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type");
return view;
} else if (viewHolder.messageBody == null || viewHolder.image == null) {
return view; //avoiding weird platform bugs
} else if (type == RECEIVED) { } else if (type == RECEIVED) {
Contact contact = message.getContact(); Contact contact = message.getContact();
if (contact != null) { if (contact != null) {
@ -528,7 +522,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (GeoHelper.isGeoUri(message.getBody())) { if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder,message); displayLocationMessage(viewHolder,message);
} else { } else {
displayTextMessage(viewHolder, message); if (message.bodyIsHeart()) {
displayHeartMessage(viewHolder, message.getBody().trim());
} else {
displayTextMessage(viewHolder, message);
}
} }
} }

View file

@ -1,8 +1,11 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
@ -17,6 +20,33 @@ import android.text.format.DateUtils;
import android.util.Pair; import android.util.Pair;
public class UIHelper { public class UIHelper {
private static String BLACK_HEART_SUIT = "\u2665";
private static String HEAVY_BLACK_HEART_SUIT = "\u2764";
private static String WHITE_HEART_SUIT = "\u2661";
public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT));
private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList(
"where are you", //en
"where are you now", //en
"where are you right now", //en
"whats your 20", //en
"what is your 20", //en
"what's your 20", //en
"whats your twenty", //en
"what is your twenty", //en
"what's your twenty", //en
"wo bist du", //de
"wo bist du jetzt", //de
"wo bist du gerade", //de
"wo seid ihr", //de
"wo seid ihr jetzt", //de
"wo seid ihr gerade", //de
"dónde estás", //es
"donde estas" //es
));
private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME
@ -225,4 +255,15 @@ public class UIHelper {
return counterpart.toString().trim(); return counterpart.toString().trim();
} }
} }
public static boolean receivedLocationQuestion(Message message) {
if (message == null
|| message.getStatus() != Message.STATUS_RECEIVED
|| message.getType() != Message.TYPE_TEXT) {
return false;
}
String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault());
body = body.replace("?","").replace("¿","");
return LOCATION_QUESTIONS.contains(body);
}
} }

View file

@ -167,7 +167,7 @@ public class XmppConnection implements Runnable {
try { try {
String srvRecordServer; String srvRecordServer;
try { try {
srvRecordServer=IDN.toASCII(namePort.getString("name")); srvRecordServer = IDN.toASCII(namePort.getString("name"));
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
// TODO: Handle me?` // TODO: Handle me?`
srvRecordServer = ""; srvRecordServer = "";
@ -187,7 +187,7 @@ public class XmppConnection implements Runnable {
+ srvRecordServer + ":" + srvRecordPort); + srvRecordServer + ":" + srvRecordPort);
} }
socket = new Socket(); socket = new Socket();
socket.connect(addr, 20000); socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
socketError = false; socketError = false;
} catch (final UnknownHostException e) { } catch (final UnknownHostException e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
@ -224,6 +224,12 @@ public class XmppConnection implements Runnable {
if (socket.isConnected()) { if (socket.isConnected()) {
socket.close(); socket.close();
} }
} catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) {
this.changeStatus(Account.State.SECURITY_ERROR);
} catch (final UnauthorizedException e) {
this.changeStatus(Account.State.UNAUTHORIZED);
} catch (final UnknownHostException | ConnectException e) { } catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND); this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) {
@ -231,6 +237,13 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE); this.changeStatus(Account.State.OFFLINE);
this.attempt--; //don't count attempt when reconnecting instantly anyway this.attempt--; //don't count attempt when reconnecting instantly anyway
} finally { } finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
if (wakeLock.isHeld()) { if (wakeLock.isHeld()) {
try { try {
wakeLock.release(); wakeLock.release();
@ -279,8 +292,7 @@ public class XmppConnection implements Runnable {
processStream(tagReader.readTag()); processStream(tagReader.readTag());
break; break;
} else if (nextTag.isStart("failure")) { } else if (nextTag.isStart("failure")) {
tagReader.readElement(nextTag); throw new UnauthorizedException();
changeStatus(Account.State.UNAUTHORIZED);
} else if (nextTag.isStart("challenge")) { } else if (nextTag.isStart("challenge")) {
final String challenge = tagReader.readElement(nextTag).getContent(); final String challenge = tagReader.readElement(nextTag).getContent();
final Element response = new Element("response"); final Element response = new Element("response");
@ -437,6 +449,10 @@ public class XmppConnection implements Runnable {
throw new IOException("interrupted mid tag"); throw new IOException("interrupted mid tag");
} }
} }
if (stanzasReceived == Integer.MAX_VALUE) {
resetStreamId();
throw new IOException("time to restart the session. cant handle >2 billion pcks");
}
++stanzasReceived; ++stanzasReceived;
lastPacketReceived = SystemClock.elapsedRealtime(); lastPacketReceived = SystemClock.elapsedRealtime();
return element; return element;
@ -538,8 +554,7 @@ public class XmppConnection implements Runnable {
if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
disconnect(true); throw new SecurityException();
changeStatus(Account.State.SECURITY_ERROR);
} }
tagReader.setInputStream(sslSocket.getInputStream()); tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream());
@ -550,8 +565,7 @@ public class XmppConnection implements Runnable {
sslSocket.close(); sslSocket.close();
} catch (final NoSuchAlgorithmException | KeyManagementException e1) { } catch (final NoSuchAlgorithmException | KeyManagementException e1) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
disconnect(true); throw new SecurityException();
changeStatus(Account.State.SECURITY_ERROR);
} }
} }
@ -590,8 +604,7 @@ public class XmppConnection implements Runnable {
" has lower priority (" + String.valueOf(saslMechanism.getPriority()) + " has lower priority (" + String.valueOf(saslMechanism.getPriority()) +
") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) + ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) +
"). Possible downgrade attack?"); "). Possible downgrade attack?");
disconnect(true); throw new SecurityException();
changeStatus(Account.State.SECURITY_ERROR);
} }
} catch (final JSONException e) { } catch (final JSONException e) {
Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
@ -603,8 +616,7 @@ public class XmppConnection implements Runnable {
} }
tagWriter.writeElement(auth); tagWriter.writeElement(auth);
} else { } else {
disconnect(true); throw new IncompatibleServerException();
changeStatus(Account.State.INCOMPATIBLE_SERVER);
} }
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
+ smVersion) + smVersion)
@ -643,10 +655,8 @@ public class XmppConnection implements Runnable {
if (packet.query().hasChild("username") if (packet.query().hasChild("username")
&& (packet.query().hasChild("password"))) { && (packet.query().hasChild("password"))) {
final IqPacket register = new IqPacket(IqPacket.TYPE.SET); final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
final Element username = new Element("username") final Element username = new Element("username").setContent(account.getUsername());
.setContent(account.getUsername()); final Element password = new Element("password").setContent(account.getPassword());
final Element password = new Element("password")
.setContent(account.getPassword());
register.query("jabber:iq:register").addChild(username); register.query("jabber:iq:register").addChild(username);
register.query().addChild(password); register.query().addChild(password);
sendIqPacket(register, new OnIqPacketReceived() { sendIqPacket(register, new OnIqPacketReceived() {
@ -659,7 +669,7 @@ public class XmppConnection implements Runnable {
changeStatus(Account.State.REGISTRATION_SUCCESSFUL); changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
} else if (packet.hasChild("error") } else if (packet.hasChild("error")
&& (packet.findChild("error") && (packet.findChild("error")
.hasChild("conflict"))) { .hasChild("conflict"))) {
changeStatus(Account.State.REGISTRATION_CONFLICT); changeStatus(Account.State.REGISTRATION_CONFLICT);
} else { } else {
changeStatus(Account.State.REGISTRATION_FAILED); changeStatus(Account.State.REGISTRATION_FAILED);
@ -673,7 +683,7 @@ public class XmppConnection implements Runnable {
disconnect(true); disconnect(true);
Log.d(Config.LOGTAG, account.getJid().toBareJid() Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": could not register. instructions are" + ": could not register. instructions are"
+ instructions.getContent()); + (instructions != null ? instructions.getContent() : ""));
} }
} }
}); });
@ -688,7 +698,7 @@ public class XmppConnection implements Runnable {
} }
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
.addChild("resource").setContent(account.getResource()); .addChild("resource").setContent(account.getResource());
this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(final Account account, final IqPacket packet) { public void onIqPacketReceived(final Account account, final IqPacket packet) {
@ -901,6 +911,11 @@ public class XmppConnection implements Runnable {
} }
private synchronized void sendPacket(final AbstractStanza packet) { private synchronized void sendPacket(final AbstractStanza packet) {
if (stanzasSent == Integer.MAX_VALUE) {
resetStreamId();
disconnect(true);
return;
}
final String name = packet.getName(); final String name = packet.getName();
if (name.equals("iq") || name.equals("message") || name.equals("presence")) { if (name.equals("iq") || name.equals("message") || name.equals("presence")) {
++stanzasSent; ++stanzasSent;
@ -1091,6 +1106,18 @@ public class XmppConnection implements Runnable {
public final ArrayList<Pair<String,String>> identities = new ArrayList<>(); public final ArrayList<Pair<String,String>> identities = new ArrayList<>();
} }
private class UnauthorizedException extends IOException {
}
private class SecurityException extends IOException {
}
private class IncompatibleServerException extends IOException {
}
public class Features { public class Features {
XmppConnection connection; XmppConnection connection;
private boolean carbonsEnabled = false; private boolean carbonsEnabled = false;

View file

@ -130,12 +130,19 @@ public final class Jid {
if (resourcepart.isEmpty() || resourcepart.length() > 1023) { if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
} }
dp = IDN.toUnicode(jid.substring(domainpartStart, slashLoc), IDN.USE_STD3_ASCII_RULES); try {
dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, slashLoc)), IDN.USE_STD3_ASCII_RULES);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
finaljid = finaljid + dp + "/" + rp; finaljid = finaljid + dp + "/" + rp;
} else { } else {
resourcepart = ""; resourcepart = "";
dp = IDN.toUnicode(jid.substring(domainpartStart, jid.length()), try{
IDN.USE_STD3_ASCII_RULES); dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, jid.length())), IDN.USE_STD3_ASCII_RULES);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
finaljid = finaljid + dp; finaljid = finaljid + dp;
} }

View file

@ -99,7 +99,7 @@ public class JingleConnection implements Downloadable {
file.delete(); file.delete();
} }
} }
Log.d(Config.LOGTAG,"sucessfully transmitted file:" + file.getAbsolutePath()); Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
if (message.getEncryption() != Message.ENCRYPTION_PGP) { if (message.getEncryption() != Message.ENCRYPTION_PGP) {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file)); intent.setData(Uri.fromFile(file));
@ -213,7 +213,7 @@ public class JingleConnection implements Downloadable {
@Override @Override
public void onPrimaryCandidateFound(boolean success, public void onPrimaryCandidateFound(boolean success,
final JingleCandidate candidate) { final JingleCandidate candidate) {
if (success) { if (success) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport( final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
JingleConnection.this, candidate); JingleConnection.this, candidate);
@ -271,6 +271,9 @@ public class JingleConnection implements Downloadable {
this.mergeCandidates(JingleCandidate.parse(content.socks5transport() this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
.getChildren())); .getChildren()));
this.fileOffer = packet.getJingleContent().getFileOffer(); this.fileOffer = packet.getJingleContent().getFileOffer();
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
if (fileOffer != null) { if (fileOffer != null) {
Element fileSize = fileOffer.findChild("size"); Element fileSize = fileOffer.findChild("size");
Element fileNameElement = fileOffer.findChild("name"); Element fileNameElement = fileOffer.findChild("name");
@ -381,6 +384,7 @@ public class JingleConnection implements Downloadable {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket packet) { public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() != IqPacket.TYPE.ERROR) { if (packet.getType() != IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
mJingleStatus = JINGLE_STATUS_INITIATED; mJingleStatus = JINGLE_STATUS_INITIATED;
mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
} else { } else {
@ -395,7 +399,9 @@ public class JingleConnection implements Downloadable {
private List<Element> getCandidatesAsElements() { private List<Element> getCandidatesAsElements() {
List<Element> elements = new ArrayList<>(); List<Element> elements = new ArrayList<>();
for (JingleCandidate c : this.candidates) { for (JingleCandidate c : this.candidates) {
elements.add(c.toElement()); if (c.isOurs()) {
elements.add(c.toElement());
}
} }
return elements; return elements;
} }

View file

@ -9,6 +9,7 @@ import android.annotation.SuppressLint;
import android.util.Log; import android.util.Log;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Downloadable;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
@ -58,7 +59,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
public JingleConnection createNewConnection(Message message) { public JingleConnection createNewConnection(Message message) {
Downloadable old = message.getDownloadable();
if (old != null) {
old.cancel();
}
JingleConnection connection = new JingleConnection(this); JingleConnection connection = new JingleConnection(this);
mXmppConnectionService.markMessage(message,Message.STATUS_WAITING);
connection.init(message); connection.init(message);
this.connections.add(connection); this.connections.add(connection);
return connection; return connection;

View file

@ -8,7 +8,9 @@ import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
@ -95,11 +97,13 @@ public class JingleInbandTransport extends JingleTransport {
file.createNewFile(); file.createNewFile();
this.fileOutputStream = file.createOutputStream(); this.fileOutputStream = file.createOutputStream();
if (this.fileOutputStream == null) { if (this.fileOutputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
return; return;
} }
this.remainingSize = this.fileSize = file.getExpectedSize(); this.remainingSize = this.fileSize = file.getExpectedSize();
} catch (final NoSuchAlgorithmException | IOException e) { } catch (final NoSuchAlgorithmException | IOException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} }
} }
@ -110,12 +114,17 @@ public class JingleInbandTransport extends JingleTransport {
this.onFileTransmissionStatusChanged = callback; this.onFileTransmissionStatusChanged = callback;
this.file = file; this.file = file;
try { try {
this.remainingSize = this.file.getSize(); if (this.file.getKey() != null) {
this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
} else {
this.remainingSize = this.file.getSize();
}
this.fileSize = this.remainingSize; this.fileSize = this.remainingSize;
this.digest = MessageDigest.getInstance("SHA-1"); this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset(); this.digest.reset();
fileInputStream = this.file.createInputStream(); fileInputStream = this.file.createInputStream();
if (fileInputStream == null) { if (fileInputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
return; return;
} }
@ -124,6 +133,7 @@ public class JingleInbandTransport extends JingleTransport {
} }
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
} }
} }
@ -150,29 +160,33 @@ public class JingleInbandTransport extends JingleTransport {
byte[] buffer = new byte[this.bufferSize]; byte[] buffer = new byte[this.bufferSize];
try { try {
int count = fileInputStream.read(buffer); int count = fileInputStream.read(buffer);
if (count == -1) { this.remainingSize -= count;
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); if (count != buffer.length && count != -1) {
fileInputStream.close(); int rem = fileInputStream.read(buffer,count,buffer.length-count);
this.onFileTransmissionStatusChanged.onFileTransmitted(file); if (rem > 0) {
} else { count += rem;
this.remainingSize -= count; }
this.digest.update(buffer); }
String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP); this.digest.update(buffer,0,count);
IqPacket iq = new IqPacket(IqPacket.TYPE.SET); String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP);
iq.setTo(this.counterpart); IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
Element data = iq.addChild("data", iq.setTo(this.counterpart);
"http://jabber.org/protocol/ibb"); Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
data.setAttribute("seq", Integer.toString(this.seq)); data.setAttribute("seq", Integer.toString(this.seq));
data.setAttribute("block-size", data.setAttribute("block-size", Integer.toString(this.blockSize));
Integer.toString(this.blockSize)); data.setAttribute("sid", this.sessionId);
data.setAttribute("sid", this.sessionId); data.setContent(base64);
data.setContent(base64); this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
this.account.getXmppConnection().sendIqPacket(iq, this.seq++;
this.onAckReceived); if (this.remainingSize > 0) {
this.seq++;
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
} else {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
fileInputStream.close();
} }
} catch (IOException e) { } catch (IOException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
FileBackend.close(fileInputStream); FileBackend.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted(); this.onFileTransmissionStatusChanged.onFileTransferAborted();
} }
@ -182,14 +196,10 @@ public class JingleInbandTransport extends JingleTransport {
try { try {
byte[] buffer = Base64.decode(data, Base64.NO_WRAP); byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
if (this.remainingSize < buffer.length) { if (this.remainingSize < buffer.length) {
buffer = Arrays buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
.copyOfRange(buffer, 0, (int) this.remainingSize);
} }
this.remainingSize -= buffer.length; this.remainingSize -= buffer.length;
this.fileOutputStream.write(buffer); this.fileOutputStream.write(buffer);
this.digest.update(buffer); this.digest.update(buffer);
if (this.remainingSize <= 0) { if (this.remainingSize <= 0) {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
@ -200,6 +210,7 @@ public class JingleInbandTransport extends JingleTransport {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
} }
} catch (IOException e) { } catch (IOException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
FileBackend.close(fileOutputStream); FileBackend.close(fileOutputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted(); this.onFileTransmissionStatusChanged.onFileTransferAborted();
} }

View file

@ -1,15 +1,20 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import android.util.Log;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
@ -53,8 +58,9 @@ public class JingleSocks5Transport extends JingleTransport {
@Override @Override
public void run() { public void run() {
try { try {
socket = new Socket(candidate.getHost(), socket = new Socket();
candidate.getPort()); SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();
outputStream = socket.getOutputStream(); outputStream = socket.getOutputStream();
byte[] login = { 0x05, 0x01, 0x00 }; byte[] login = { 0x05, 0x01, 0x00 };
@ -102,6 +108,7 @@ public class JingleSocks5Transport extends JingleTransport {
digest.reset(); digest.reset();
fileInputStream = file.createInputStream(); fileInputStream = file.createInputStream();
if (fileInputStream == null) { if (fileInputStream == null) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
return; return;
} }
@ -121,10 +128,13 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransmitted(file); callback.onFileTransmitted(file);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} catch (IOException e) { } catch (IOException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} finally { } finally {
FileBackend.close(fileInputStream); FileBackend.close(fileInputStream);
@ -150,6 +160,7 @@ public class JingleSocks5Transport extends JingleTransport {
fileOutputStream = file.createOutputStream(); fileOutputStream = file.createOutputStream();
if (fileOutputStream == null) { if (fileOutputStream == null) {
callback.onFileTransferAborted(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
return; return;
} }
double size = file.getExpectedSize(); double size = file.getExpectedSize();
@ -160,6 +171,7 @@ public class JingleSocks5Transport extends JingleTransport {
count = inputStream.read(buffer); count = inputStream.read(buffer);
if (count == -1) { if (count == -1) {
callback.onFileTransferAborted(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
return; return;
} else { } else {
fileOutputStream.write(buffer, 0, count); fileOutputStream.write(buffer, 0, count);
@ -173,10 +185,13 @@ public class JingleSocks5Transport extends JingleTransport {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
callback.onFileTransmitted(file); callback.onFileTransmitted(file);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} catch (IOException e) { } catch (IOException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted(); callback.onFileTransferAborted();
} finally { } finally {
FileBackend.close(fileOutputStream); FileBackend.close(fileOutputStream);

View file

@ -6,6 +6,9 @@ import eu.siacs.conversations.xmpp.jid.Jid;
import android.util.Base64; import android.util.Base64;
public class Avatar { public class Avatar {
public enum Origin { PEP, VCARD };
public String type; public String type;
public String sha1sum; public String sha1sum;
public String image; public String image;
@ -13,21 +16,14 @@ public class Avatar {
public int width; public int width;
public long size; public long size;
public Jid owner; public Jid owner;
public Origin origin = Origin.PEP; //default to maintain compat
public byte[] getImageAsBytes() { public byte[] getImageAsBytes() {
return Base64.decode(image, Base64.DEFAULT); return Base64.decode(image, Base64.DEFAULT);
} }
public String getFilename() { public String getFilename() {
if (type == null) { return sha1sum;
return sha1sum;
} else if (type.equalsIgnoreCase("image/webp")) {
return sha1sum + ".webp";
} else if (type.equalsIgnoreCase("image/png")) {
return sha1sum + ".png";
} else {
return sha1sum;
}
} }
public static Avatar parseMetadata(Element items) { public static Avatar parseMetadata(Element items) {
@ -64,10 +60,44 @@ public class Avatar {
return null; return null;
} }
avatar.type = child.getAttribute("type"); avatar.type = child.getAttribute("type");
avatar.sha1sum = child.getAttribute("id"); String hash = child.getAttribute("id");
if (!isValidSHA1(hash)) {
return null;
}
avatar.sha1sum = hash;
avatar.origin = Origin.PEP;
return avatar; return avatar;
} }
} }
return null; return null;
} }
@Override
public boolean equals(Object object) {
if (object != null && object instanceof Avatar) {
Avatar other = (Avatar) object;
return other.getFilename().equals(this.getFilename());
} else {
return false;
}
}
public static Avatar parsePresence(Element x) {
Element photo = x != null ? x.findChild("photo") : null;
String hash = photo != null ? photo.getContent() : null;
if (hash == null) {
return null;
}
if (!isValidSHA1(hash)) {
return null;
}
Avatar avatar = new Avatar();
avatar.sha1sum = hash;
avatar.origin = Origin.VCARD;
return avatar;
}
private static boolean isValidSHA1(String s) {
return s != null && s.matches("[a-fA-F0-9]{40}");
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 B

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 B

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Some files were not shown because too many files have changed in this diff Show more